skip to content

How does retrieve() work with bodyToMono and bodyToFlux to decode a response?

level: juniorimportance: must knowfreq 70%

answer

  1. retrieve() = 'give me the body'
  2. bodyToMono = single, bodyToFlux = stream
  3. 4xx/5xx -> WebClientResponseException
  4. ParameterizedTypeReference for generics
  5. HttpMessageReader/Jackson decodes

basics

~10 s

retrieve() triggers the request and gives you the response body directly. bodyToMono(Type.class) decodes a single object; bodyToFlux(Type.class) decodes a stream/collection into many elements. retrieve() auto-throws WebClientResponseException on 4xx/5xx.

solid answer

~40 s

After building the request (method + uri + optional body/headers), `retrieve()` is the shortcut that says 'I just want the body.' You then decode it: `bodyToMono(User.class)` for a single value (`Mono<User>`), or `bodyToFlux(User.class)` for a stream of values (`Flux<User>`), e.g. a JSON array or a Server-Sent-Events feed decoded element-by-element. `retrieve()` applies default status handling: any 4xx or 5xx status turns into an error signal — a `WebClientResponseException` (or a subclass like `WebClientResponseException.NotFound`) — propagated through the reactive chain. You can customize that with `.onStatus(predicate, handler)` before `bodyToMono`. Decoding uses the registered `HttpMessageReader`s (Jackson for JSON by default). `bodyToMono(Void.class)` discards the body when you only care that it completed.

code

java · 19 lines
java
// Single object
Mono<User> one = client.get().uri("/users/{id}", 7)
    .retrieve()
    .bodyToMono(User.class);

// Streamed array, element-by-element
Flux<User> many = client.get().uri("/users")
    .retrieve()
    .bodyToFlux(User.class);

// Generic collection as one value (needs ParameterizedTypeReference)
Mono<List<User>> list = client.get().uri("/users")
    .retrieve()
    .bodyToMono(new ParameterizedTypeReference<List<User>>() {});

// Fire-and-forget: only care that it completed
Mono<Void> deleted = client.delete().uri("/users/{id}", 7)
    .retrieve()
    .bodyToMono(Void.class);

go deeper

for a junior

Know bodyToMono for one object, bodyToFlux for many, and retrieve() throws on error statuses.

for a middle

Explain ParameterizedTypeReference, streaming vs buffering, and the default error exception type.

for a senior

Discuss codec/maxInMemorySize limits and choosing Flux for back-pressured streaming.

for a principal

Reason about memory/throughput trade-offs of streaming decode and custom codec configuration at scale.

## The retrieve() shortcut `retrieve()` is called on a `WebClient.RequestHeadersSpec` after you've chosen the method and URI. It means: *execute the request and expose the response body directly*, letting the framework handle status errors with sensible defaults. It returns a `WebClient.ResponseSpec`. ## bodyToMono vs bodyToFlux From the `ResponseSpec` you extract the body: - **`bodyToMono(Class<T>)` / `bodyToMono(ParameterizedTypeReference<T>)`** → `Mono<T>`. Use for a single object, or for generics like `List<User>`: ```java .bodyToMono(new ParameterizedTypeReference<List<User>>() {}) ``` - **`bodyToFlux(Class<T>)`** → `Flux<T>`. Use when the response is a *sequence*: a JSON array decoded lazily element-by-element, or a streaming media type like `text/event-stream` (Server-Sent Events) where each event is one element. `Flux` lets you process items as they arrive and applies back-pressure. ### Mono<List<User>> vs Flux<User> Both can consume a JSON array. `bodyToMono(List<User>)` buffers the whole array then emits one `List`. `bodyToFlux(User.class)` emits each `User` as it is parsed — better for large or streaming payloads because it does not hold the whole collection in memory. ## How decoding works Decoding is delegated to registered **`HttpMessageReader`** implementations, chosen by the response `Content-Type`. Out of the box: Jackson (`Jackson2JsonDecoder`) for `application/json`, plus readers for strings, byte arrays, form data, etc. You can override them via `.codecs(...)` on the builder (e.g. to raise the in-memory buffer limit with `maxInMemorySize`). ## Default error handling With `retrieve()`, **any 4xx or 5xx** response is converted into an **error signal**: a `WebClientResponseException` carrying the status code, headers, and raw body. Subtypes exist per status (`WebClientResponseException.NotFound`, `.BadRequest`, etc.). This error flows down the reactive chain, so you handle it with `.onErrorResume(...)`, `.onErrorMap(...)`, etc. You can pre-empt the default by inserting `.onStatus(HttpStatusCode::isError, resp -> ...)` to map specific statuses to custom exceptions. ## Gotchas - **`bodyToMono(Void.class)`** signals completion without a body — use it for fire-and-check calls (e.g. a DELETE). - Default max in-memory buffer is 256 KB; large bodies throw `DataBufferLimitException` unless you raise `maxInMemorySize`. - Generic types need `ParameterizedTypeReference` because of type erasure — `bodyToMono(List.class)` loses the element type. - The body must be consumed. `retrieve()` handles that for you; the manual `exchange` variants do not (covered separately).

  • When would you prefer bodyToFlux(User.class) over bodyToMono(List<User>)?
    For large or streaming responses. bodyToFlux decodes and emits each element as it arrives with back-pressure, avoiding buffering the whole collection in memory; bodyToMono(List) waits for and holds the entire array.
  • How do you decode a generic type like Map<String,Integer>?
    Use ParameterizedTypeReference to defeat type erasure: bodyToMono(new ParameterizedTypeReference<Map<String,Integer>>() {}). Passing Map.class would lose the type parameters.

saying these in an interview costs you the question

  • Thinking bodyToMono can only decode single scalars, not collections
  • Not knowing retrieve() turns 4xx/5xx into WebClientResponseException
  • Using bodyToMono(List.class) and expecting typed elements (erasure)
  • Believing retrieve() blocks and returns the body synchronously

context