How do HttpMessageConverters and ClientHttpRequestInterceptors work in RestTemplate, and what are they used for?
answer
- converter = body format (Jackson JSON); ordered canRead/canWrite
- interceptor = around every request (auth, logging, tracing)
- execution.execute() = proceed down the chain
- logging response body needs BufferingClientHttpRequestFactory
- wire both via RestTemplateBuilder additional*
basics
~10 sMessage converters serialize/deserialize request and response bodies (Jackson for JSON). Interceptors wrap every request/response so you can add cross-cutting behavior like auth headers, logging, or correlation IDs without touching each call site.
solid answer
~40 sRestTemplate holds an ordered list of HttpMessageConverters that turn Java objects into request bytes and response bytes back into objects, chosen by the body's type and the Content-Type/Accept headers; MappingJackson2HttpMessageConverter handles JSON by default in Boot. You can add or reorder converters (e.g. XML, protobuf, a custom Jackson ObjectMapper). ClientHttpRequestInterceptors form a chain around every request: each gets the request, body, and a ClientHttpRequestExecution to proceed, so they're ideal for cross-cutting concerns — injecting Authorization or tracing headers, logging, metrics, retries. Order matters and a broken interceptor can swallow the request. Both are wired via RestTemplateBuilder (additionalMessageConverters, additionalInterceptors) or by mutating the RestTemplate's lists. Note the default SimpleClientHttpRequestFactory can't re-read the body, so a logging interceptor that reads the response stream needs a buffering factory.
code
java · 18 lines@Bean
RestTemplate restTemplate(RestTemplateBuilder builder, ObjectMapper mapper) {
RestTemplate rest = builder
// custom JSON converter with shared ObjectMapper
.additionalMessageConverters(new MappingJackson2HttpMessageConverter(mapper))
// cross-cutting: auth + correlation id on every request
.additionalInterceptors((request, body, execution) -> {
request.getHeaders().setBearerAuth(TokenHolder.current());
request.getHeaders().add("X-Correlation-Id", Tracing.id());
return execution.execute(request, body);
})
.build();
// allow interceptors/loggers to read the response body more than once
rest.setRequestFactory(new BufferingClientHttpRequestFactory(
new SimpleClientHttpRequestFactory()));
return rest;
}go deeper
Know Jackson converter turns JSON into objects; interceptors add headers.
Configure a custom ObjectMapper converter and a header-injecting interceptor via the builder.
Explain the converter list ordering, the interceptor chain/execution.execute, and the buffering-factory gotcha.
Design a shared client config (converters + interceptors + factory) as a reusable module so auth/tracing/serialization are uniform across services.
## HttpMessageConverters — body marshalling A `HttpMessageConverter<T>` knows how to **read** an HTTP body into an object and **write** an object into an HTTP body for particular media types. RestTemplate keeps an **ordered list** of them. On a request it picks the first converter that `canWrite(type, contentType)`; on a response the first that `canRead(type, contentType)`. Defaults registered by Spring Boot include: - `MappingJackson2HttpMessageConverter` — JSON (default when Jackson is present) - `StringHttpMessageConverter` — `String` bodies - `ByteArrayHttpMessageConverter`, `ResourceHttpMessageConverter` - `AllEncompassingFormHttpMessageConverter` — form/multipart - Optionally XML (`MappingJackson2XmlHttpMessageConverter`/JAXB) if on classpath You customize them to, say, register a shared `ObjectMapper` (snake_case, JavaTimeModule, fail-on-unknown off), add protobuf/XML, or change ordering: ```java rest = builder .messageConverters(new MappingJackson2HttpMessageConverter(myObjectMapper)) .build(); // or additionalMessageConverters(...) to append ``` ## ClientHttpRequestInterceptor — cross-cutting behavior An interceptor wraps **every** request: ```java public interface ClientHttpRequestInterceptor { ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException; } ``` You mutate the request (add headers), call `execution.execute(request, body)` to proceed down the chain to the actual HTTP call, then optionally inspect/modify the response. Typical uses: **auth** (`request.getHeaders().setBearerAuth(...)`), **tracing/correlation IDs**, **logging**, **metrics/timing**, simple **retry**. They run in list order and form a pipeline (like servlet filters). Register via `builder.additionalInterceptors(...)` or `restTemplate.getInterceptors().add(...)`. ```java rest = builder.additionalInterceptors((request, body, execution) -> { request.getHeaders().setBearerAuth(tokenSupplier.get()); request.getHeaders().add("X-Correlation-Id", correlationId()); return execution.execute(request, body); }).build(); ``` ## The buffering gotcha The default request factory (`SimpleClientHttpRequestFactory`, over `HttpURLConnection`) exposes the response body as a **one-shot stream**. A logging interceptor that reads the response body consumes it, so your caller then sees an empty body. Wrap the factory in a `BufferingClientHttpRequestFactory` so the body can be read multiple times: ```java rest.setRequestFactory(new BufferingClientHttpRequestFactory( new SimpleClientHttpRequestFactory())); ``` ## Converters vs interceptors — division of labor - **Converter** = *what the body looks like* (serialization format, media type). - **Interceptor** = *what happens around the call* (headers, logging, retry) regardless of body format. ## When to use - Custom JSON mapping / non-JSON formats → converter. - Auth, tracing, logging, metrics applied uniformly → interceptor. - Reading response bodies in an interceptor → add `BufferingClientHttpRequestFactory`. - Connection pooling / timeouts / PATCH support → choose the underlying `ClientHttpRequestFactory` (Apache HttpClient or JDK HttpClient), a separate concern from both.
- A logging interceptor prints the response body, but downstream callers now get an empty body. Why?The default SimpleClientHttpRequestFactory exposes the response as a one-shot stream; reading it in the interceptor consumes it. Wrap the factory in BufferingClientHttpRequestFactory so the body can be read again.
- Where would you inject an OAuth bearer token — converter or interceptor?An interceptor. Auth is a cross-cutting per-request concern; a converter only deals with body serialization.
saying these in an interview costs you the question
- Confusing converters (body format) with interceptors (around-the-call behavior)
- Reading the response body in an interceptor without buffering
- Thinking interceptors change the serialized body format
- Claiming Jackson converter is registered even without Jackson on the classpath