skip to content

How do you send a POST request with a JSON body and custom headers using RestClient, and how is the request body serialized?

level: middleimportance: must knowfreq 60%

answer

  1. post().uri().contentType().body(payload).retrieve()
  2. body() overloaded: request payload vs response extract
  3. HttpMessageConverter / Jackson serializes POJO
  4. headers: contentType/accept/header/headers(Consumer)
  5. ParameterizedTypeReference for generics

basics

~10 s

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

For 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 lines
java
record 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

for a junior

Show the post().uri().body(payload).retrieve().body(Type) chain and know Jackson serializes JSON.

for a middle

Explain header setters, the request-vs-response body() overloads, and ParameterizedTypeReference for generics.

for a senior

Discuss converter selection by content type, form/MultiValueMap bodies, streaming bodies, and default headers via the builder.

for a principal

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

context