Mono vs Flux — Choosing the Right Type and Converting Between Them
Fast recall
Quick revision
Core answer
Void completion signal. Pick `Fluxflux.collectList() collapses a Flux to a Mono>
; mono.flatMapMany(...) expands a Mono into a Flux; Mono.zip(a, b) aggregates several Monos. Choosing the wrong type forces awkward conversions and obscures the signal cardinality your API exposes.Whether the candidate understands cardinality semantics, how the two types interoperate, and how the type choice shapes the API contract
Returning Flux when only one value is produced; calling collectList() inside a chain unnecessarily; using block() to convert types
Discuss Mono<Void> for fire-and-forget, Mono<ResponseEntity<Flux<T>>> for streaming responses, and how cardinality maps to HTTP semantics
Interview-ready guide
Interview answer
Cardinality drives the choice. Mono for 0 or 1, Flux for 0 to N.
When to pick each
MonoforfindById, single HTTP GET, aggregate of N async calls, fire-and-forget side effects (Mono)Fluxfor streaming HTTP, paginated DB scans, SSE / WebSocket frames, Kafka subscriptions, file streaming
Conversions
flux.collectList()→Mono— only for bounded results- >
mono.flatMapMany(fn)→Flux— expand a single value into a streamMono.zip(a, b, ...)— combine multiple Monos into a tupleMono.when(...)— wait for many to complete, returnMonoflux.count()/flux.hasElements()— aggregate to a single Mono
WebFlux endpoint patterns
- Single resource →
Monowithapplication/json - Collection materialized →
Fluxwithapplication/json - True streaming →
Fluxwith NDJSON or SSEContent-Type - Side-effect with no body →
Mono> - Not found →
Mono.empty()plusswitchIfEmpty(Mono.error(NotFoundException))
Pitfalls to call out
collectList()on unbounded Flux silently blows memoryMonoon a streaming endpoint kills NDJSON / SSE- >
Monois almost always a misuse; flatten it> block()to convert defeats reactive entirely
Strategic point
- Cardinality is part of your API contract
- Picking the right type at the start avoids awkward conversions later
- It also maps cleanly to HTTP semantics — single resource, collection, streaming, no content
Mono vs Flux — Choosing the Right Type and Converting Between Them
Direct Answer
Pick Mono<T> when an operation produces at most one value — a single DB lookup, an HTTP GET that returns one resource, an aggregate of multiple async calls, or a Void completion signal. Pick Flux<T> when an operation produces many values over time — a streaming HTTP response, paginated DB rows, SSE / WebSocket frames, or a Kafka subscription. The two interconvert: flux.collectList() collapses a Flux<T> to a Mono<List<T>>; mono.flatMapMany(...) expands a Mono<T> into a Flux<R>; Mono.zip(a, b) aggregates several Monos. Choosing the wrong type forces awkward conversions and obscures the signal cardinality your API exposes.
Common Mistakes & Pitfalls
Returning Flux when only one value is produced; calling collectList() inside a chain unnecessarily; using block() to convert types
Check your understanding
Answer these without looking back at the article.
- When is collectList() acceptable on a Flux and when is it dangerous?
- How do you stream a large database query as NDJSON over HTTP in WebFlux?
- What does Mono.zip do and when is it the right tool?
- Why is Mono<Flux<T>> usually a smell, and how do you flatten it?
Related interview prep categories
1Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.