skip to content

TestRestTemplate is described as 'fault-tolerant.' What does that mean, and how does it differ from a plain RestTemplate on 4xx/5xx responses?

level: seniorimportance: must knowfreq 60%

answer

  1. no-op error handler, never throws
  2. RestTemplate throws HttpClientErrorException/HttpServerErrorException
  3. assert on getStatusCode(), no try/catch
  4. use exchange/getForEntity not getForObject on errors
  5. redirects OFF by default

basics

~20 s

Fault-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 s

A 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
java
@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

for a junior

Know it doesn't throw on error responses; you check getStatusCode().

for a middle

Name DefaultResponseErrorHandler vs the no-op handler and the getForObject pitfall.

for a senior

Explain error-body deserialization, redirect/cookie defaults, and why this shape suits negative tests.

for a principal

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.

context