skip to content

When and how do you use RestTemplate.exchange(), and how do you send custom request headers?

level: middleimportance: should knowfreq 55%

answer

  1. exchange = verb + HttpEntity + responseType -> ResponseEntity
  2. headers live in HttpEntity(body, headers)
  3. generic list -> ParameterizedTypeReference (exchange only)
  4. PATCH needs Apache/JDK factory, not default
  5. per-request headers -> HttpEntity; every-request -> interceptor

basics

~20 s

Use exchange() when the simple verb methods aren't enough — e.g. to set custom headers or read a generic type. You wrap the body and headers in an HttpEntity, pass an HttpMethod, and get back a ResponseEntity.

solid answer

~40 s

exchange() is RestTemplate's general-purpose method: exchange(url, HttpMethod, HttpEntity, responseType, uriVars). Reach for it when the shortcut methods (getForObject, postForEntity) can't express what you need — most commonly to attach request headers (Authorization, custom content type) via an HttpEntity, or to control the exact verb. To send headers you build an HttpHeaders, set your values, wrap the body in new HttpEntity<>(body, headers), and pass it in. It always returns a ResponseEntity so you get status and headers back. For generic response types like List<User> you can't use List.class due to erasure; pass a ParameterizedTypeReference<List<User>>() instead — that overload is only on exchange(), which is another reason to use it.

code

java · 13 lines
java
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(token);
headers.setAccept(List.of(MediaType.APPLICATION_JSON));

// GET a generic collection with an auth header
ResponseEntity<List<Order>> resp = rest.exchange(
    "/customers/{id}/orders",
    HttpMethod.GET,
    new HttpEntity<>(headers),
    new ParameterizedTypeReference<List<Order>>() {},
    customerId);

List<Order> orders = resp.getBody();

go deeper

for a junior

Know exchange() lets you set the HTTP method and pass an HttpEntity.

for a middle

Be fluent in HttpEntity for headers and ParameterizedTypeReference for generic types; know it returns ResponseEntity.

for a senior

Discuss when interceptors beat per-call HttpEntity, and factory dependence for PATCH.

for a principal

Standardize header/auth injection via interceptors and factory choice across all clients rather than ad-hoc exchange calls.

## Why exchange() exists The per-verb helpers (`getForObject`, `postForEntity`, `put`, `delete`) are convenient but limited: several of them give you no place to set **request headers**, and none of them accept a generic `ParameterizedTypeReference`. `exchange(...)` is the **general form** that covers every case: ``` <T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) ``` It always returns a `ResponseEntity<T>`, so you can inspect the status code and response headers. ## Sending custom request headers Headers travel in an **`HttpEntity`**, which bundles an optional body with `HttpHeaders`: ```java HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(token); headers.setContentType(MediaType.APPLICATION_JSON); headers.setAccept(List.of(MediaType.APPLICATION_JSON)); HttpEntity<CreateOrder> request = new HttpEntity<>(body, headers); ResponseEntity<Order> resp = rest.exchange("/orders", HttpMethod.POST, request, Order.class); ``` For a header-only request (e.g. a GET with an Authorization header) use `new HttpEntity<>(headers)` with no body. > Note: for headers that must be on **every** request (auth, correlation id), a `ClientHttpRequestInterceptor` registered on the RestTemplate is cleaner than building an HttpEntity each call. ## Generic response types and type erasure Because of Java generics erasure you cannot write `getForObject(url, List<User>.class)` — that syntax is illegal, and `List.class` loses the element type so Jackson would give you `List<LinkedHashMap>`. The fix is **`ParameterizedTypeReference`**, an abstract class you subclass anonymously to capture the full type: ```java ResponseEntity<List<User>> resp = rest.exchange( "/users", HttpMethod.GET, null, new ParameterizedTypeReference<List<User>>() {}); List<User> users = resp.getBody(); ``` This overload exists **only** on `exchange()`, so needing a generic collection forces you to it. ## HttpMethod flexibility `exchange` takes the verb explicitly, so it also handles methods without a dedicated helper (e.g. `PATCH`, `OPTIONS`) — though `PATCH` requires an underlying request factory that supports it (the default `SimpleClientHttpRequestFactory` based on `HttpURLConnection` does not; Apache HttpClient or JDK `HttpClient` factories do). ## Gotchas - A `null` requestEntity is allowed (common for GET); passing headers-only means `new HttpEntity<>(headers)`. - The response type must match what converters can produce; a mismatch throws during conversion. - Remember exchange still applies the same error handling — 4xx/5xx throw `HttpClientErrorException`/`HttpServerErrorException`.

  • Why can't you deserialize into List<User> with getForObject(url, List.class)?
    Type erasure — List.class carries no element type, so Jackson produces List<LinkedHashMap>. Use exchange() with a ParameterizedTypeReference<List<User>>() to preserve the generic type.
  • If every request needs an Authorization header, is HttpEntity the best place?
    No — repeating it per call is error-prone. Register a ClientHttpRequestInterceptor on the RestTemplate to inject the header on every request centrally.

saying these in an interview costs you the question

  • Thinking you can write List<User>.class or that List.class preserves the element type
  • Believing getForObject can set request headers
  • Assuming the default request factory supports PATCH
  • Saying ParameterizedTypeReference works with getForObject (it's an exchange-only overload)

context