How do you send a POST request with a JSON body and custom headers using RestClient, and how is the request body serialized?
answer
- post().uri().contentType().body(payload).retrieve()
- body() overloaded: request payload vs response extract
- HttpMessageConverter / Jackson serializes POJO
- headers: contentType/accept/header/headers(Consumer)
- ParameterizedTypeReference for generics
basics
~10 sUse restClient.post().uri(...).contentType(MediaType.APPLICATION_JSON).header(...).body(payloadObject).retrieve().body(ResponseType.class). The payload object is serialized to JSON by an HttpMessageConverter (Jackson).
solid answer
~40 sFor a POST you chain post() → uri(...) → set headers (contentType/accept/header/headers) → body(payload) → retrieve() → extraction. The body(Object) call takes a domain object; RestClient picks a matching HttpMessageConverter — Jackson's MappingJackson2HttpMessageConverter for application/json — to serialize it to the wire. Setting contentType(MediaType.APPLICATION_JSON) tells the converter and sets the Content-Type header. You can also pass a raw String or byte[] as the body. Headers are set fluently: .contentType(...), .accept(...), single .header(name, values...), or bulk .headers(h -> ...). After retrieve(), extract with body(Class) to get just the deserialized response object, or toEntity(Class) to get a ResponseEntity with status and headers. Note body() is overloaded: on the request side it sets the payload, on the ResponseSpec side it extracts the response.
code
java · 14 linesrecord NewUser(String name, String email) {}
record CreatedUser(Long id, String name) {}
ResponseEntity<CreatedUser> response = restClient.post()
.uri("/users")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.headers(h -> h.setBearerAuth(token))
.body(new NewUser("ann", "[email protected]"))
.retrieve()
.toEntity(CreatedUser.class);
System.out.println(response.getStatusCode()); // 201 CREATED
CreatedUser created = response.getBody();go deeper
Show the post().uri().body(payload).retrieve().body(Type) chain and know Jackson serializes JSON.
Explain header setters, the request-vs-response body() overloads, and ParameterizedTypeReference for generics.
Discuss converter selection by content type, form/MultiValueMap bodies, streaming bodies, and default headers via the builder.
Standardize serialization (custom ObjectMapper, converter ordering) and enforce content-type discipline across services to avoid 415/double-encoding classes of bugs.
## Building a POST ```java CreatedUser result = restClient.post() .uri("/users") .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .header("Idempotency-Key", key) .body(new NewUser("ann", "[email protected]")) // request payload .retrieve() .body(CreatedUser.class); // response extraction ``` ### Setting headers (fluent) - `.contentType(MediaType)` — sets `Content-Type`; tells the converter which format to write. - `.accept(MediaType...)` — sets `Accept`; influences which converter reads the response. - `.header(name, value...)` — one header, one or more values. - `.headers(Consumer<HttpHeaders>)` — bulk mutation (e.g. `h -> h.setBearerAuth(token)`). - Builder-level `defaultHeader(...)` applies to every request from that client. ### The request body: `body(...)` The request-side `body(Object)` accepts: - a **POJO** → serialized via a matching **`HttpMessageConverter`**. For JSON that's `MappingJackson2HttpMessageConverter` (Jackson). The chosen `Content-Type` drives selection. - a **`String`** or **`byte[]`** → written mostly as-is. - a `body(Object, ParameterizedTypeReference<T>)` overload to preserve generic type info (e.g. `List<User>`). - a low-level `body(StreamingHttpOutputMessage.Body)` for streaming writes. **`HttpMessageConverter`** is the abstraction that turns Java objects into an HTTP body and back. Boot auto-registers Jackson for JSON, plus String, byte[], form, etc. RestClient reuses this exact machinery from `spring-web`. ### Extraction after `retrieve()` - `body(Class<T>)` / `body(ParameterizedTypeReference<T>)` → just the deserialized response object; response headers/status are discarded. - `toEntity(Class<T>)` → a `ResponseEntity<T>` with status code, headers, and body. - `toBodilessEntity()` → `ResponseEntity<Void>` when you only care about status/headers. ## The `body` overload gotcha `body(...)` appears on **two** different specs: 1. **RequestBodySpec.body(payload)** — sets what you send. 2. **ResponseSpec.body(Class)** — extracts what you received. They're distinct steps in the chain; confusing them is a common source of "why won't this compile" or "why is my payload the response type" mistakes. ## Form data For `application/x-www-form-urlencoded`, pass a `MultiValueMap<String,String>` as the body with `contentType(MediaType.APPLICATION_FORM_URLENCODED)`; the `FormHttpMessageConverter` handles it. ## Gotchas - If you forget `contentType`, Jackson may still be selected for a POJO, but being explicit avoids surprises and 415 responses from strict servers. - Passing an already-serialized JSON `String` while also expecting Jackson to serialize it double-encodes — pass the POJO, not a hand-built string. - `ParameterizedTypeReference` is needed for generic collections to survive type erasure.
- How do you deserialize a response into a generic type like List<User>?Use the ParameterizedTypeReference overload: .body(new ParameterizedTypeReference<List<User>>() {}), because a raw Class<T> loses generic info to type erasure.
- What converter serializes a POJO body to JSON, and where does it come from?MappingJackson2HttpMessageConverter (Jackson), auto-registered by Spring Boot; RestClient reuses the shared HttpMessageConverter list from spring-web.
saying these in an interview costs you the question
- Manually JSON-stringifying the POJO and passing the String, causing double encoding
- Thinking body() on the request side and body() extraction are the same method
- Using Class<T> for a generic collection instead of ParameterizedTypeReference
- Believing RestClient has its own serializer separate from HttpMessageConverter