TestRestTemplate is described as 'fault-tolerant.' What does that mean, and how does it differ from a plain RestTemplate on 4xx/5xx responses?
answer
- no-op error handler, never throws
- RestTemplate throws HttpClientErrorException/HttpServerErrorException
- assert on getStatusCode(), no try/catch
- use exchange/getForEntity not getForObject on errors
- redirects OFF by default
basics
~20 sFault-tolerant means it does not throw exceptions on error responses. A plain RestTemplate throws HttpClientErrorException on 4xx and HttpServerErrorException on 5xx. TestRestTemplate installs a no-op error handler, so you just read response.getStatusCode() and assert on the error status.
solid answer
~40 sA default RestTemplate uses DefaultResponseErrorHandler, which treats 4xx/5xx as errors and throws HttpClientErrorException or HttpServerErrorException — awkward in tests where an error status is exactly what you want to assert. TestRestTemplate replaces that with a no-op error handler, so error responses come back as a normal ResponseEntity: you call getStatusCode(), getHeaders(), and getBody() and assert on them, no try/catch needed. Two practical consequences: prefer getForEntity/exchange over getForObject for error paths, because getForObject discards the status and may hand you a null or partially-deserialized body. And the error body is still there — you can deserialize the JSON error envelope into a DTO via exchange with the right type. This makes negative tests (401, 403, 404, 400 validation) clean and assertion-friendly.
code
java · 17 lines@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ErrorPathTest {
@Autowired TestRestTemplate rest;
@Test
void unknownUserReturns404_noExceptionThrown() {
// No try/catch: TestRestTemplate does not throw on 4xx.
ResponseEntity<ApiError> response = rest.exchange(
"/api/users/999", HttpMethod.GET, null, ApiError.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
assertThat(response.getBody().code()).isEqualTo("USER_NOT_FOUND");
}
record ApiError(String code, String message) {}
}go deeper
Know it doesn't throw on error responses; you check getStatusCode().
Name DefaultResponseErrorHandler vs the no-op handler and the getForObject pitfall.
Explain error-body deserialization, redirect/cookie defaults, and why this shape suits negative tests.
Discuss test-design implications: asserting error envelopes/contract, and consistency of negative-path coverage across the suite.
### What 'fault-tolerant' means here Every `RestTemplate` runs each response through a **`ResponseErrorHandler`**. The default (`DefaultResponseErrorHandler`) classifies **4xx** and **5xx** status codes as errors and **throws**: - 4xx → `HttpClientErrorException` (with subclasses like `HttpClientErrorException.NotFound`, `.Unauthorized`, `.Forbidden`, `.BadRequest`). - 5xx → `HttpServerErrorException`. In application code that is convenient. In tests it is the opposite of what you want — a `404` or `403` is frequently the **expected** outcome you are verifying, and being forced to wrap every call in try/catch to inspect the status is noisy and error-prone. ### What TestRestTemplate changes `TestRestTemplate` installs a **no-op error handler** (internally a `NoOpResponseErrorHandler`) so that `hasError(...)` always returns false. Therefore **no exception is thrown for any status code**. The full `ResponseEntity` is returned regardless, and you assert directly: ```java ResponseEntity<String> r = rest.getForEntity("/api/missing", String.class); assertThat(r.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); ``` ### getForObject vs getForEntity/exchange on error paths This is the key gotcha: - **`getForEntity` / `exchange`** return a `ResponseEntity`, so you always have `getStatusCode()`, `getHeaders()`, and `getBody()`. Use these for anything where the status matters — i.e., almost all negative tests. - **`getForObject` / `postForObject`** return only the **body**. On an error you lose the status entirely, and the body may be `null` or a deserialization of the error JSON into your success type (garbage or null). So `getForObject` is a poor fit for error-path tests. ### The error body is still available Because nothing throws, the error response body is intact. You can deserialize the error envelope: ```java ResponseEntity<ApiError> r = rest.exchange("/api/users/999", HttpMethod.GET, null, ApiError.class); assertThat(r.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); assertThat(r.getBody().code()).isEqualTo("USER_NOT_FOUND"); ``` ### Other 'fault-tolerant' touches - **Redirects are not followed by default.** TestRestTemplate disables auto-redirects so you can assert on `3xx` responses (e.g., a `302` to a login page). You can opt back in with `TestRestTemplate.HttpClientOption.ENABLE_REDIRECTS`. - **Cookies**: not stored between calls unless you enable `HttpClientOption.ENABLE_COOKIES` (relevant for session-based flows). ### Comparison table (mental model) | Aspect | plain RestTemplate | TestRestTemplate | |---|---|---| | 4xx/5xx | throws | returns ResponseEntity | | Error handler | DefaultResponseErrorHandler | no-op | | Redirects | followed (client default) | disabled by default | | Intended use | production calls | tests | ### When this matters in an interview Candidates who say 'you have to catch HttpClientErrorException' are describing RestTemplate, not TestRestTemplate — that reveals they have not actually written TestRestTemplate negative tests.
- Why prefer getForEntity/exchange over getForObject for a test that expects a 4xx?getForObject returns only the body and discards the status, so you cannot assert the status code and the body may be null or wrongly deserialized. getForEntity/exchange give you the full ResponseEntity including getStatusCode().
- Does TestRestTemplate follow HTTP redirects by default?No — auto-redirects are disabled so you can assert on 3xx responses; enable them with TestRestTemplate.HttpClientOption.ENABLE_REDIRECTS if needed.
saying these in an interview costs you the question
- Saying you must catch HttpClientErrorException with TestRestTemplate (that's plain RestTemplate behavior).
- Using getForObject and expecting to still read the error status.
- Assuming TestRestTemplate follows redirects like a normal client by default.