skip to content

retrieve vs exchangeToMono

retrieve() covers the normal case while exchangeToMono hands you the whole response when you need status, headers and body together — and obliges you to consume the body. Interviewers ask about that obligation, because a leaked connection is the penalty.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

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

open as a page

What is Spring's WebClient and why is it the recommended replacement for RestTemplate?

level: juniorimportance: must knowfreq 78%

basics

~20 s

WebClient is Spring's non-blocking, reactive HTTP client. Unlike RestTemplate, which blocks a thread per request, WebClient uses async I/O and returns Mono/Flux, so a few threads handle many concurrent calls. RestTemplate is in maintenance mode.

open as a page

What is the difference between retrieve() and exchangeToMono()/exchangeToFlux(), and when would you use each?

level: middleimportance: must knowfreq 82%

basics

~20 s

retrieve() is the shortcut that gives you just the body and auto-errors on 4xx/5xx. exchangeToMono/exchangeToFlux give you the full ClientResponse — status, headers, cookies — so you decide how to decode based on those. Use retrieve() for the common case; exchange when you need full control.

open as a page

How do you customize error handling for specific HTTP statuses with retrieve(), and how does it compare to doing it via exchangeToMono?

level: seniorimportance: should knowfreq 60%

basics

~20 s

With retrieve(), chain .onStatus(statusPredicate, response -> Mono<Throwable>) before bodyToMono to map chosen statuses to custom exceptions. retrieve() still auto-releases the body. exchangeToMono can do the same but you must consume the body yourself and decode manually.

open as a page

You are migrating a blocking Spring MVC service from RestTemplate to WebClient. What are the key pitfalls and how do you avoid them?

level: principalimportance: should knowfreq 48%

basics

~20 s

Reuse one WebClient (not per-request), map RestTemplate calls to get()/post()+retrieve()+bodyToMono, translate error handling to onStatus, set explicit timeouts and connection-pool limits, and if you must stay blocking, call .block() carefully — never on a reactive event-loop thread.

open as a page