skip to content

How do you extract the request body inside a HandlerFunction, and when do you use bodyToMono versus bodyToFlux?

level: middleimportance: must knowfreq 55%

answer

  1. bodyToMono = single object
  2. bodyToFlux = array / stream (ndjson, SSE)
  3. lazy, non-blocking, decoded by HttpMessageReaders
  4. body is one-shot — consume once
  5. empty body -> switchIfEmpty; never .block()

basics

~10 s

Call request.bodyToMono(MyType.class) when the body is a single object, or request.bodyToFlux(MyType.class) when the body is a stream/array of many objects. Both return reactive publishers you then chain onto.

solid answer

~40 s

ServerRequest exposes bodyToMono(Class<T>) and bodyToFlux(Class<T>) to decode the raw request body into your types using the configured HTTP message readers (e.g. Jackson for JSON). Use bodyToMono for a single object payload — it yields a Mono<T>. Use bodyToFlux for a collection/stream (a JSON array, or a streaming media type like application/x-ndjson) — it yields a Flux<T>. The decoding is lazy: nothing reads the body until you subscribe, which the framework does when it subscribes to your returned Mono<ServerResponse>. You typically flatMap the decoded body into your service call and then into the response builder. For generics use the ParameterizedTypeReference overload. Reading the body is a one-shot operation — the reactive stream can only be consumed once.

code

java · 21 lines
java
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

public Mono<ServerResponse> createUser(ServerRequest request) {
    return request.bodyToMono(CreateUser.class)          // Mono<CreateUser>
            .flatMap(userService::save)                   // Mono<User>
            .flatMap(user -> ServerResponse
                    .status(HttpStatus.CREATED)
                    .bodyValue(user))
            // no body sent -> 400
            .switchIfEmpty(ServerResponse.badRequest().build());
}

// Streaming many items in from an NDJSON upload:
public Mono<ServerResponse> bulkImport(ServerRequest request) {
    return request.bodyToFlux(Item.class)                 // Flux<Item>
            .flatMap(itemService::save)
            .then(ServerResponse.accepted().build());
}

go deeper

for a junior

Know bodyToMono for one object, bodyToFlux for many, and that both return reactive types.

for a middle

Explain laziness, the one-shot nature, empty-body handling with switchIfEmpty, and the ParameterizedTypeReference overload for generics.

for a senior

Discuss error propagation to 400, streaming media types with bodyToFlux, and why blocking is forbidden.

for a principal

Reason about backpressure on large streaming imports, choosing bodyToFlux vs Mono<List> for memory, and custom codec/HttpMessageReader configuration.

**The problem.** In the functional model you don't get `@RequestBody` binding — you pull the body off `ServerRequest` yourself, as a reactive stream. **The two extractors.** - `Mono<T> bodyToMono(Class<T> elementClass)` — decodes the body as a *single* value. Returns a `Mono<T>` (0..1 elements). Use for a normal single-object JSON payload. - `Flux<T> bodyToFlux(Class<T> elementClass)` — decodes the body as a *stream* of values. Returns a `Flux<T>` (0..N). Use for a JSON array, or a streaming content type such as `application/x-ndjson`/Server-Sent Events where elements arrive over time. - Both have `ParameterizedTypeReference<T>` overloads for generic types (e.g. `bodyToMono(new ParameterizedTypeReference<List<Item>>() {})`). Note: to read a JSON array you can *either* `bodyToFlux(Item.class)` *or* `bodyToMono` a `List` via `ParameterizedTypeReference` — the Flux form streams element-by-element; the Mono<List> buffers the whole thing. **How decoding works.** Extraction delegates to the reactive `HttpMessageReader`s registered on the `ServerCodecConfigurer` (Jackson for JSON by default). It respects the request's `Content-Type`. It is **lazy and non-blocking**: calling `bodyToMono` does not read bytes — it returns a publisher. The actual read happens when something subscribes, which the framework does when it subscribes to the `Mono<ServerResponse>` your handler returns. **Typical usage pattern.** Chain the body into your logic and fold it into the response: ```java request.bodyToMono(CreateOrder.class) .flatMap(orderService::create) // Mono<Order> .flatMap(order -> ServerResponse.ok().bodyValue(order)); ``` **Edge cases & gotchas.** - *One-shot*: the request body is a single-consumption stream. Calling `bodyToMono` twice, or `bodyToMono` then `bodyToFlux`, does not re-read the network body — don't try to consume it multiple times. - *Empty body*: for a `GET`/`DELETE` with no body, `bodyToMono` completes empty (emits nothing). Downstream operators that assume a value (`flatMap` producing the response) will be skipped — you often need `switchIfEmpty(...)` to return a 400/response when a body was required. - *Errors*: malformed JSON causes the returned publisher to emit an error (e.g. `ServerWebInputException` / `DecodingException`), which propagates and — unless handled — yields a 400. Handle with `onErrorResume` if you need a custom response. - *Blocking is forbidden*: never call `.block()` on the body inside a handler — that defeats the non-blocking model and can deadlock on event-loop threads. - *Streaming semantics*: `bodyToFlux` truly streams for streaming media types, applying backpressure; for a plain JSON array Jackson still parses incrementally.

  • What happens if the JSON body is malformed?
    The publisher returned by bodyToMono/bodyToFlux emits an error (a DecodingException wrapped as ServerWebInputException). If unhandled it maps to HTTP 400 Bad Request. You can intercept it with onErrorResume to shape a custom error response.
  • Why can't you just call request.bodyToMono(...).block() to get the value synchronously?
    Blocking on an event-loop thread stalls the non-blocking runtime and risks deadlock/thread starvation. WebFlux expects you to compose with flatMap/map and return a Mono; the framework subscribes for you.

saying these in an interview costs you the question

  • Calling .block() on the body inside a handler
  • Trying to read the body twice (it's a one-shot stream)
  • Using bodyToMono for a streaming/array payload where per-element processing is needed
  • Forgetting the empty-body case (no switchIfEmpty), so a required body silently produces no response

context