skip to content

How do you convert between Mono and Flux? Explain flatMapMany, next, and collectList and when each applies.

level: middleimportance: should knowfreq 60%

answer

  1. Mono->Flux: flatMapMany (fan-out) / mono.flux()
  2. Flux->Mono first: next()
  3. Flux->Mono all: collectList() (waits for complete)
  4. exactly one: single(); side-effect done: then()
  5. collectList on infinite Flux = hang

basics

~20 s

Mono to Flux: use flatMapMany when the single item expands into many (returns a Flux), or Flux.from(mono). Flux to Mono: next() takes the first element (0..1), collectList() gathers all elements into a Mono<List>, and single()/last() take exactly one/the last.

solid answer

~40 s

Direction Mono->Flux: `mono.flatMapMany(item -> someFlux(item))` when one value fans out into a stream (e.g. a Mono<Order> becoming a Flux<LineItem>); `Flux.from(mono)` or `mono.flux()` when you just need the Flux type without fan-out. Direction Flux->Mono: `flux.next()` returns a Mono of the **first** emitted element (empty if none) and cancels the rest; `flux.collectList()` buffers **all** elements into a `Mono<List<T>>` (completes only when the Flux completes — beware unbounded/infinite streams); `flux.single()` demands **exactly one** (errors otherwise), `flux.last()` takes the last, `flux.reduce(...)`/`collect(...)` aggregate. Choice depends on cardinality intent: first-of-many (next), collect-all (collectList), enforce-one (single). collectList materializes everything in memory, so it's unsuitable for large or infinite Fluxes.

code

java · 20 lines
java
import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;
import java.util.List;

// Mono -> Flux (fan-out): one order becomes many line items
Mono<Order> order = orderRepo.findById(id);
Flux<LineItem> items = order.flatMapMany(o -> lineItemRepo.findByOrder(o.id()));

// Mono -> Flux (retype, no fan-out)
Flux<User> asFlux = userMono.flux();

// Flux -> Mono: first element only (cancels the rest)
Mono<User> firstUser = userFlux.next();

// Flux -> Mono: collect all into a List (finite streams only!)
Mono<List<User>> allUsers = userFlux.collectList();

// Flux -> Mono: enforce exactly one, or just await completion
Mono<User> theOne = userFlux.single();     // errors if 0 or >1
Mono<Void> done   = userFlux.then();       // ignore items, signal completion

go deeper

for a junior

Knows collectList() gathers a Flux into a Mono<List> and next() takes the first.

for a middle

Explains flatMapMany for fan-out and the cardinality intent behind next/collectList/single.

for a senior

Warns about collectList memory/infinite-stream hazards and distinguishes flatMap vs flatMapMany on Mono.

for a principal

Designs streaming-friendly pipelines that avoid materialization, chooses backpressure-preserving operators, and reasons about cancellation semantics of next().

## Why convert at all? Method signatures dictate cardinality. A repository returns `Flux<Row>` but a caller needs a single aggregate `Mono<Report>`; or a `Mono<UserId>` must expand into a `Flux<Permission>`. Reactor gives directional operators for each transition. ## Mono -> Flux ### `flatMapMany` (the workhorse) ```java Mono<Order> order = orderRepo.findById(id); Flux<LineItem> items = order.flatMapMany(o -> lineItemRepo.findByOrder(o.id())); ``` `Mono<T>.flatMapMany(Function<T, Publisher<R>>)` takes the single value, applies a function returning a `Publisher` (often a Flux), and **flattens** it into a `Flux<R>`. This is the idiomatic "one thing becomes many things" conversion. If the Mono is empty, the result Flux is empty. ### `Flux.from(mono)` / `mono.flux()` ```java Flux<User> f1 = Flux.from(userMono); // static factory Flux<User> f2 = userMono.flux(); // convenience method ``` No fan-out — just re-types a 0..1 Mono as a 0..1 Flux. Use when an API demands `Flux` but you have a Mono and don't need to expand it. ## Flux -> Mono ### `next()` — first element as a Mono ```java Mono<User> first = userFlux.next(); ``` Emits the **first** `onNext` as a `Mono<T>`, then **cancels** upstream (stops pulling further elements). If the Flux is empty, the Mono completes empty. Great for "give me the head" without draining the stream. ### `collectList()` — buffer everything ```java Mono<List<User>> all = userFlux.collectList(); ``` Accumulates **every** emitted element into a `List`, emitting the list only when the source Flux **completes**. Consequences: (1) it **waits for completion** — never use on an infinite/streaming Flux (it'd never emit); (2) it holds all elements **in memory** — risky for large result sets. Related collectors: `collectMap`, `collectSortedList`, `collect(Collectors....)`. ### `single()`, `singleOrEmpty()`, `last()` - `single()` — expects **exactly one** element; errors with `NoSuchElementException` if empty or `IndexOutOfBoundsException` if more than one. Use to assert cardinality. - `singleOrEmpty()` — 0 or 1 allowed, errors only if 2+. - `last()` — the final element (errors if empty; `last(default)` for a fallback). ### `reduce` / `then` - `reduce(seed, accumulator)` → `Mono<R>` aggregate (sum, fold). - `flux.then()` → `Mono<Void>` that ignores all elements and just signals completion — for "run this stream for its side effects, tell me when done." - `flux.count()` → `Mono<Long>`. ## Cardinality-driven decision guide - Need the **first**, discard rest → `next()` - Need **all in a List** (finite, bounded) → `collectList()` - Need to **enforce exactly one** → `single()` - Need an **aggregate/fold** → `reduce`/`collect` - Just need **completion** signal → `then()` - Single value **fans out** to many → `flatMapMany` - Retype 0..1 without fan-out → `Flux.from` / `mono.flux()` ## Gotchas - `collectList()` on an **infinite Flux** hangs forever (no completion). `next()` or `take(n).collectList()` are safe alternatives. - `single()` throwing on 2+ elements surprises people expecting "first"; use `next()` for first-of-many. - `flatMap` vs `flatMapMany`: on a Mono, `flatMap` returns a **Mono** (inner publisher must be a Mono / 0..1), whereas `flatMapMany` returns a **Flux** (inner can emit many). Picking `flatMap` when the inner emits multiple values only keeps the first.

  • What's the risk of calling collectList() on a Flux, and how do you mitigate it?
    collectList() buffers every element in memory and only emits once the source completes — so it OOMs on very large result sets and hangs forever on infinite/streaming Fluxes. Mitigate by using next()/take(n), streaming operators, or bounded pagination instead of materializing the whole stream.
  • On a Mono, when do you use flatMap versus flatMapMany?
    flatMap keeps the result a Mono — the mapping function must return a 0..1 Publisher; if it emits multiple values only the first is kept. flatMapMany returns a Flux and is the correct choice when the single input expands into many output elements.

saying these in an interview costs you the question

  • Using collectList() on an infinite or streaming Flux
  • Using single() when 'first of many' was intended (single errors on 2+ elements)
  • Thinking flatMap on a Mono can produce many output elements (it keeps only one)
  • Believing next() drains/processes the whole Flux (it cancels after the first element)

context