skip to content

Request/Response Body Streaming

bodyValue sends a ready value, body(Publisher) streams one, and a streaming response can be consumed as a Flux without buffering it all. This is how you move large payloads with flat memory usage.

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

questions

4

When sending a request body with WebClient, what is the difference between bodyValue(obj) and body(publisher, Class)? When would you use each?

level: juniorimportance: must knowfreq 70%

answer

  1. bodyValue = ready object
  2. body(publisher,Class) = Mono/Flux
  3. bodyValue rejects reactive types → IllegalArgumentException
  4. fromValue vs fromPublisher under the hood
  5. Flux body = streamed upload

basics

~20 s

bodyValue takes a single already-available object and serializes it. body(publisher, Class) takes a reactive stream (Mono/Flux) that WebClient subscribes to and serializes element by element. Use bodyValue for one plain object, body(...) for reactive/streaming data.

solid answer

~40 s

bodyValue(Object) is for a concrete, materialized value you already hold in memory — WebClient serializes it (e.g. via Jackson) into the request body. It rejects reactive types: passing a Mono/Flux to bodyValue throws IllegalArgumentException. body(Publisher, Class) (or body(Publisher, ParameterizedTypeReference)) is for a reactive producer: WebClient subscribes to the Mono/Flux and writes each emitted element, enabling true streaming without collecting everything first. So bodyValue(dto) for a single POJO; body(fluxOfDtos, Dto.class) when the payload comes from a stream (DB cursor, another WebClient call) you want to pass through lazily. Under the hood bodyValue is essentially BodyInserters.fromValue and body(publisher,...) is BodyInserters.fromPublisher, but the fluent methods are preferred.

code

java · 14 lines
java
// Single, already-available object
Mono<Void> a = webClient.post().uri("/users")
    .bodyValue(new UserDto("neo"))       // serialized as-is
    .retrieve().bodyToMono(Void.class);

// Reactive/streaming producer (Flux) -> streamed upload
Flux<UserDto> users = userRepository.findAll(); // e.g. R2DBC
Mono<Void> b = webClient.post().uri("/users/bulk")
    .contentType(MediaType.APPLICATION_NDJSON) // stream as NDJSON
    .body(users, UserDto.class)          // subscribes to the Flux
    .retrieve().bodyToMono(Void.class);

// This THROWS IllegalArgumentException:
// .bodyValue(Mono.just(new UserDto("trinity")))

go deeper

for a junior

Should know bodyValue = plain object, body(publisher,Class) = reactive stream, and that mixing them up throws.

for a middle

Should mention BodyInserters.fromValue/fromPublisher and that Flux enables streaming upload.

for a senior

Adds media-type implications (JSON array vs NDJSON) and laziness/backpressure of body(Flux).

for a principal

Frames it as encoder/HttpMessageWriter strategy selection and when streaming upload actually matters for memory/latency.

**Context.** `WebClient` is Spring WebFlux's non-blocking, reactive HTTP client (the reactive analog of `RestTemplate`). When you build a request you eventually specify the request body. There are two fluent ways on `RequestBodySpec`: - **`bodyValue(Object body)`** — you pass an already-computed value (a POJO, a `String`, `byte[]`, a `Map`, etc.). WebClient hands it to an `HttpMessageWriter` (typically `Jackson2JsonEncoder` for JSON) which serializes it into the body. It is *synchronous input*: the object must already exist in memory. Crucially, `bodyValue` **rejects reactive types** — if you pass a `Mono` or `Flux`, it throws `IllegalArgumentException` at runtime telling you to use `body(...)` instead. - **`body(Publisher<T> publisher, Class<T> elementClass)`** (and overloads taking `ParameterizedTypeReference<T>`) — you pass a reactive **`Publisher`** (`Mono<T>` or `Flux<T>`). WebClient will **subscribe** to it when the request is executed and encode each emitted element into the outbound body. A `Mono<T>` behaves like a single value; a `Flux<T>` **streams** multiple elements, so the body is written incrementally as elements arrive — you never need to collect the whole thing into memory. **Relationship to `BodyInserters`.** A `BodyInserter` is the lower-level strategy object that actually writes the body to the `ClientHttpRequest`. The fluent methods are thin sugar: - `bodyValue(x)` ≈ `body(BodyInserters.fromValue(x))` - `body(publisher, Class)` ≈ `body(BodyInserters.fromPublisher(publisher, Class))` You rarely call `BodyInserters` directly today; prefer `bodyValue` / `body(publisher, ...)`. (`BodyInserters.fromValue` replaced the deprecated `syncBody`/`fromObject`.) **When to use each.** - Single materialized object → `bodyValue(dto)`. - The data is produced reactively (a `Flux` from R2DBC, another WebClient response, a generator) and you want lazy, backpressured, streaming upload → `body(flux, Dto.class)`. - A single async value you don't want to `.block()` on → `body(mono, Dto.class)` (or `bodyValue(mono.block())` only if blocking is acceptable, which it usually is not in reactive code). **Gotchas.** - Passing a reactive type to `bodyValue` = `IllegalArgumentException`. This is the most common mistake. - `body(...)` needs the element `Class`/`ParameterizedTypeReference` because generic type is erased and the encoder must know what it's writing. - The `Content-Type` still matters: for a `Flux` streamed as JSON you typically get a JSON array unless you set a streaming media type like `application/x-ndjson`. - Nothing is sent until the returned response `Mono`/`Flux` is subscribed — WebClient is lazy end to end.

  • What happens if you pass a Mono to bodyValue()?
    It throws IllegalArgumentException. bodyValue expects an already-resolved value; reactive publishers must go through body(publisher, Class) so WebClient can subscribe to them.
  • What replaced the old syncBody() method?
    bodyValue() (and BodyInserters.fromValue()). syncBody and BodyInserters.fromObject were deprecated/removed in favor of bodyValue/fromValue, which also handle empty publishers more consistently.

saying these in an interview costs you the question

  • Thinking bodyValue can accept a Mono/Flux
  • Believing body(publisher) collects the whole stream into memory before sending
  • Confusing bodyValue with bodyToMono (request vs response)
  • Saying syncBody is the current method

context

open as a page

How do you consume a streaming HTTP response with WebClient as a Flux<T>, and how does bodyToFlux differ from bodyToMono in backpressure and memory behavior?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Call retrieve() (or exchangeToFlux) then bodyToFlux(T.class). It decodes the response body incrementally into a stream of T with backpressure, so you process elements as they arrive without buffering the whole response. bodyToMono decodes into a single value/object, buffering that one payload.

open as a page

How do you stream a large, incrementally-produced dataset as a WebClient request body without buffering it all in memory, and what media type makes it a true stream?

level: middleimportance: should knowfreq 45%

basics

~20 s

Pass a Flux to body(flux, Type.class) so WebClient writes elements as they are emitted instead of collecting them. Use a streaming media type like application/x-ndjson (or text/event-stream) so each element is flushed separately rather than buffered into one JSON array.

open as a page

Explain DataBuffer-level body transfer in WebFlux: what DataBuffer/DataBufferUtils are, how to stream a response body as Flux<DataBuffer> to disk, and the memory-management rules you must follow.

level: principalimportance: should knowfreq 30%

basics

~20 s

DataBuffer is WebFlux's abstraction over a chunk of bytes (often a pooled Netty ByteBuf). You can get the raw body as Flux<DataBuffer> and write it out with DataBufferUtils (e.g. write to a channel). Because buffers are pooled/reference-counted, you must release each one after use or you leak memory.

open as a page