skip to content

How do you build a ServerResponse, and what is the difference between .body(...) and .bodyValue(...)?

level: middleimportance: must knowfreq 50%

answer

  1. status factory -> headers -> terminal body
  2. bodyValue = concrete object already in hand
  3. body(publisher, Class) = Mono/Flux + element type
  4. never bodyValue(a Mono)
  5. .build() for empty body; terminal returns Mono<ServerResponse>

basics

~10 s

Use the builder: ServerResponse.ok() (or .status(...), .created(uri), etc.), set headers, then a terminal call. Use bodyValue(obj) for a plain already-available object; use body(publisher, Class) when the body is a Mono/Flux.

solid answer

~40 s

ServerResponse is built through a fluent builder starting from a status factory — ServerResponse.ok(), .status(HttpStatus), .created(location), .noContent(), .badRequest() — on which you chain headers, contentType, cookies, then finish with a terminal body method that returns Mono<ServerResponse>. The key distinction: bodyValue(Object) takes a concrete, already-resolved value and serializes it directly — use it when you already hold the object. body(Publisher, Class) / body(Publisher, ParameterizedTypeReference) takes a reactive Mono or Flux plus its element type — use it to stream a publisher as the body without unwrapping it first. There's also body(BodyInserter). A frequent mistake is passing a Mono to bodyValue: it would try to serialize the Mono object itself, not its contents. Use .build() for an empty body.

code

java · 26 lines
java
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class UserHandler {

    // bodyValue: we already hold the resolved User
    public Mono<ServerResponse> getUser(ServerRequest request) {
        String id = request.pathVariable("id");
        return userService.findById(id)                 // Mono<User>
                .flatMap(user -> ServerResponse.ok()
                        .contentType(MediaType.APPLICATION_JSON)
                        .bodyValue(user))               // concrete object
                .switchIfEmpty(ServerResponse.notFound().build());
    }

    // body(publisher, Class): stream a Flux directly
    public Mono<ServerResponse> listUsers(ServerRequest request) {
        Flux<User> users = userService.findAll();
        return ServerResponse.ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(users, User.class);               // publisher + element type
    }
}

go deeper

for a junior

Know the builder shape and that bodyValue takes a real object, body takes a publisher.

for a middle

Explain why body needs the element Class (type erasure) and the switchIfEmpty pattern for 404.

for a senior

Contrast resolve-then-bodyValue vs pass-publisher-to-body for empty/not-found handling and streaming content types.

for a principal

Discuss BodyInserters as the underlying abstraction, streaming media types, and encoder/codec selection for the response.

**The builder.** `ServerResponse` is immutable and constructed via a builder obtained from a status factory method: - Status factories: `ServerResponse.ok()`, `ServerResponse.status(HttpStatus.CREATED)`, `ServerResponse.created(uri)` (sets 201 + Location), `ServerResponse.accepted()`, `ServerResponse.noContent()`, `ServerResponse.badRequest()`, `ServerResponse.notFound()`, `ServerResponse.unprocessableEntity()`. - Header/config builder methods (all return the builder): `.contentType(MediaType.APPLICATION_JSON)`, `.header(name, values)`, `.headers(consumer)`, `.cookie(ResponseCookie)`, `.cacheControl(...)`, `.eTag(...)`, `.lastModified(...)`. - **Terminal** methods (return `Mono<ServerResponse>`): `.bodyValue(obj)`, `.body(publisher, class)`, `.body(BodyInserter)`, `.render(name, model)` (views), and `.build()` (no body). Once you call a terminal method you have the `Mono<ServerResponse>` your handler returns. **bodyValue vs body — the core distinction.** - `bodyValue(Object body)` — the argument is a **concrete, already-materialized** value (a DTO, a String, a List). It is written using the configured encoders directly. Use when you *already have* the object in hand (e.g. inside a `flatMap` where you've resolved it). - `body(Publisher<T> publisher, Class<T> elementClass)` — the argument is a **reactive publisher** (`Mono<T>` or `Flux<T>`) plus the element type so the encoder knows what it's serializing. Use to hand a stream straight to the response *without* first unwrapping it. The `Class`/`ParameterizedTypeReference` is needed because generic type info is erased at runtime and the encoder must know the element type. - `body(BodyInserter<?, ? super ServerHttpResponse> inserter)` — lowest-level; `BodyInserters.fromPublisher(...)`, `fromValue(...)`, etc. `bodyValue`/`body(publisher, class)` are convenience wrappers over inserters. **Two equivalent styles.** You can either resolve the value first then use `bodyValue`, or pass the publisher to `body`: ```java // Style A: resolve then bodyValue service.find(id).flatMap(u -> ServerResponse.ok().bodyValue(u)); // Style B: pass publisher to body ServerResponse.ok().body(service.find(id), User.class); ``` Style A gives you `switchIfEmpty` control over the empty case (return 404 when the Mono is empty). Style B is terser but a `Mono` that emits nothing still produces a 200 with an empty body, so it's less convenient for not-found handling. **Gotchas.** - **Never pass a `Mono`/`Flux` to `bodyValue`.** It would serialize the publisher object itself (nonsense/serialization error), not the emitted content. For publishers use `body(...)`. - `bodyValue(null)` is not allowed — use `.build()` for an empty body. - The terminal call returns `Mono<ServerResponse>`; don't forget your handler must *return* it. - For a `Flux` streamed as SSE/NDJSON, set the appropriate `.contentType(...)` (e.g. `MediaType.APPLICATION_NDJSON` or `TEXT_EVENT_STREAM`) so it streams rather than buffering into a JSON array. - `ServerResponse.created(location)` conveniently sets both the 201 status and the `Location` header.

  • What goes wrong if you write ServerResponse.ok().bodyValue(userMono)?
    bodyValue treats its argument as the literal body object, so it tries to serialize the Mono instance itself rather than the User it will emit — you get a broken/empty response or a serialization error. Use body(userMono, User.class) instead.
  • How do you return a 201 Created with a Location header?
    ServerResponse.created(uri).bodyValue(entity) — the created(URI) factory sets status 201 and the Location header in one call, then you attach the body.

saying these in an interview costs you the question

  • Passing a Mono/Flux to bodyValue instead of body(...)
  • Thinking body(...) and bodyValue(...) are interchangeable for concrete objects vs publishers
  • Calling bodyValue(null) instead of .build() for empty bodies
  • Forgetting the element Class argument on body(publisher, ...) and expecting generics to survive erasure

context