When sending a request body with WebClient, what is the difference between bodyValue(obj) and body(publisher, Class)? When would you use each?
answer
- bodyValue = ready object
- body(publisher,Class) = Mono/Flux
- bodyValue rejects reactive types → IllegalArgumentException
- fromValue vs fromPublisher under the hood
- Flux body = streamed upload
basics
~20 sbodyValue 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 sbodyValue(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// 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
Should know bodyValue = plain object, body(publisher,Class) = reactive stream, and that mixing them up throws.
Should mention BodyInserters.fromValue/fromPublisher and that Flux enables streaming upload.
Adds media-type implications (JSON array vs NDJSON) and laziness/backpressure of body(Flux).
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