skip to content

Reactor Operators

The operator toolkit: map versus flatMap and its ordering-preserving cousins, filter, zip, merge, switchMap, buffer and window, plus the difference between assembly time and subscription time. Interviewers ask you to pick between flatMap and concatMap and defend the choice.

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

questions

5

In Project Reactor, what is the difference between map and flatMap on a Flux or Mono?

level: juniorimportance: must knowfreq 85%

answer

  1. map = 1:1 sync, returns value
  2. flatMap = 1:N async, returns Publisher
  3. flatMap subscribes + merges inner streams
  4. map would give Flux<Mono<T>>
  5. flatMap interleaves, no order guarantee

basics

~10 s

map transforms each item synchronously, one value in -> one value out. flatMap transforms each item into a Publisher (another Flux/Mono, often async) and flattens all those inner streams into one output stream.

solid answer

~40 s

map is a 1-to-1 synchronous transformation: you return a plain value and the operator wraps it back into the stream. flatMap is a 1-to-N asynchronous transformation: your function returns a Publisher (a Mono or Flux), typically an async call like a WebClient request or an R2DBC query, and flatMap subscribes to each of those inner publishers and merges their emissions into a single output Flux. Use map when the work is a pure, in-memory computation; use flatMap when each element triggers another reactive/async operation so you avoid ending up with a Flux<Mono<T>>. A key gotcha: flatMap subscribes to inner publishers eagerly and interleaves results, so it does NOT preserve source order by default.

code

java · 11 lines
java
// map: synchronous 1-to-1
Flux<Integer> doubled = Flux.just(1, 2, 3)
        .map(n -> n * 2);            // 2, 4, 6

// flatMap: asynchronous 1-to-N, flattens inner Publishers
Flux<User> users = Flux.just("u1", "u2", "u3")
        .flatMap(id -> userRepository.findById(id)); // Mono<User> per id

// WRONG: map with an async call yields a nested stream of publishers
Flux<Mono<User>> nested = Flux.just("u1")
        .map(id -> userRepository.findById(id));     // almost never useful

go deeper

for a junior

Must know map = sync 1:1, flatMap = async returning a Publisher. Recognizing that flatMap is for I/O calls is the core takeaway.

for a middle

Should add that flatMap subscribes eagerly, interleaves, and has a concurrency parameter; and that map for an async call yields a useless nested Flux<Mono<T>>.

for a senior

Frames the choice around ordering and back-pressure, contrasts with concatMap/flatMapSequential, and knows the concurrency default (256).

for a principal

Discusses subscription semantics, scheduler/blocking implications, and prefetch/concurrency tuning for throughput vs. resource limits.

## Setup **Project Reactor** is the reactive-streams library underneath **Spring WebFlux**. Its two publisher types are **Mono<T>** (0 or 1 item) and **Flux<T>** (0..N items). Operators are chained transformations you build on top of these publishers. ## map `map(Function<T,R>)` is a **synchronous, one-to-one** transformation. For every element `T` the source emits, your function returns a plain value `R`, and `map` re-wraps it into the stream. Nothing async happens; there is no new Publisher. ```java Flux.just(1, 2, 3).map(n -> n * 2); // -> 2, 4, 6 ``` Think of it as `List.stream().map(...)` but on a reactive stream. ## flatMap `flatMap(Function<T, Publisher<R>>)` is an **asynchronous, one-to-many** transformation. Your function returns a **Publisher** (another `Mono` or `Flux`) for each element. `flatMap` then **subscribes to each inner publisher** and **merges** their emissions into one flat output stream — hence the name (it flattens `Flux<Publisher<R>>` into `Flux<R>`). ```java Flux.just("a", "b") .flatMap(id -> webClient.get().uri("/x/{id}", id).retrieve().bodyToMono(String.class)); ``` Use it whenever each element needs another **reactive/async call**: a `WebClient` request, an R2DBC repository lookup, etc. If you used `map` for that, you'd get a `Flux<Mono<R>>` — a stream of un-subscribed publishers — which is almost never what you want. ## Why the distinction matters - **Concurrency:** `flatMap` subscribes to inner publishers **eagerly**, up to a concurrency limit (default 256, overridable via `flatMap(fn, concurrency)`). Multiple inner calls run in flight at once. - **Ordering:** because inner streams complete at different times, `flatMap` **interleaves** results — output order does NOT match source order. If you need order, use `concatMap` (sequential) or `flatMapSequential` (concurrent but reordered). - **map never reorders** because it's synchronous 1-to-1. ## Gotchas - Returning a value from `flatMap` where you meant `map` (or vice versa) is a classic compile error/confusion: `flatMap` expects a Publisher, `map` expects a plain value. - Blocking inside `map` (e.g. a JDBC call) stalls the pipeline — that work belongs in `flatMap` on a proper scheduler, or better, a reactive driver. - `Mono.flatMap` returns a Mono (0..1); `Mono.flatMapMany` is used when the inner publisher is a Flux and you want many results out of one input. ## When to use - **map** — pure transformation, formatting, field extraction, DTO mapping (no I/O). - **flatMap** — per-element async I/O where order doesn't matter and throughput does.

  • If you use flatMap over a WebClient call, is the output in the same order as the input?
    No. flatMap subscribes to inner publishers eagerly and merges as they complete, so faster responses emit first. Use concatMap for strict order or flatMapSequential to keep order while still running concurrently.
  • What's the difference between Mono.flatMap and Mono.flatMapMany?
    Mono.flatMap maps the single value to another Mono (0..1 out). Mono.flatMapMany maps it to a Publisher that can emit many items, returning a Flux — useful when one input fans out to many results.

saying these in an interview costs you the question

  • Thinking map can do async I/O
  • Believing flatMap preserves source order
  • Not knowing flatMap subscribes to inner publishers
  • Confusing which operator returns a Publisher vs a value

context

open as a page

Contrast flatMap, concatMap, and flatMapSequential in terms of ordering and concurrency. When would you pick each?

level: middleimportance: must knowfreq 78%

basics

~10 s

flatMap: concurrent inner subscriptions, output order not preserved. concatMap: one inner at a time (sequential), order preserved, no concurrency. flatMapSequential: concurrent inner subscriptions but output re-ordered to match source order.

open as a page

Explain the difference between assembly time and subscription time in Reactor. Why does it matter for correctness, and how do defer/fromCallable relate to it?

level: principalimportance: must knowfreq 55%

basics

~20 s

Assembly time is when you build the operator chain (the reactive pipeline is just a blueprint). Subscription time is when someone calls subscribe() and data actually flows. Nothing runs until subscription. Mono.defer/Mono.fromCallable delay eager work so it happens per-subscription instead of at assembly.

open as a page

What do buffer and window do, and how does transform differ from transformDeferred? Give a use case for each.

level: seniorimportance: should knowfreq 40%

basics

~20 s

buffer groups incoming elements into Lists (by size or time) and emits Flux<List<T>>. window is similar but emits Flux<Flux<T>> — each group is itself a stream. transform lets you factor a reusable chain of operators into a function applied once at assembly time; transformDeferred applies it per subscriber.

open as a page

Explain zip, merge, and switchMap. How do they combine or select among streams, and what are their typical use cases?

level: seniorimportance: should knowfreq 62%

basics

~20 s

zip pairs one item from each source into a combined tuple, emitting when all have a value. merge interleaves items from multiple sources concurrently as they arrive. switchMap maps each element to a Publisher but cancels the previous inner when a new element arrives, keeping only the latest.

open as a page