skip to content

Mono vs Flux Types

Mono carries zero or one element and Flux carries many, with factory methods and conversions between them. Expect a question on why Mono.just evaluates eagerly and when you need defer or fromCallable instead.

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

questions

5

What is the difference between Mono and Flux in Project Reactor, and when would you use each?

level: juniorimportance: must knowfreq 85%

answer

  1. Mono = 0..1, Flux = 0..N
  2. both are cold + lazy Publishers
  3. Mono<Void> = completion only
  4. single entity -> Mono, stream/list -> Flux
  5. nothing runs until subscribe

basics

~20 s

Both are reactive publishers. Mono emits 0 or 1 item then completes; Flux emits 0 to many items then completes. Use Mono for a single result (like one entity), Flux for a stream or collection of items.

solid answer

~40 s

Mono and Flux are the two publisher types in Project Reactor, the reactive library Spring WebFlux is built on. Both implement Reactive Streams' Publisher and emit items asynchronously with backpressure. Mono<T> represents an async computation of 0 or 1 element (plus a completion or error signal) — ideal for a single entity lookup, a count, or a fire-and-forget that returns Void. Flux<T> represents a stream of 0..N elements — ideal for query results, server-sent events, or paginated data. Both are lazy and cold by default: nothing runs until you subscribe. In a WebFlux controller, returning Mono<User> yields one JSON object, while Flux<User> yields a JSON array (or an SSE/NDJSON stream). Choosing the right cardinality makes the API's contract explicit at the type level.

code

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

// Mono: at most one element
Mono<String> one = Mono.just("hello");     // emits "hello", completes
Mono<String> none = Mono.empty();          // completes with no value

// Flux: zero-to-many elements
Flux<Integer> many = Flux.just(1, 2, 3);   // emits 1,2,3, completes

// Typical WebFlux controller shapes
@GetMapping("/users/{id}")
Mono<User> byId(@PathVariable String id) {
    return userRepository.findById(id);    // 0..1 -> single JSON object
}

@GetMapping("/users")
Flux<User> all() {
    return userRepository.findAll();       // 0..N -> JSON array
}

go deeper

for a junior

Must state cardinality (0..1 vs 0..N) and give one use case each.

for a middle

Should add that both are cold/lazy Publishers and mention Mono<Void> plus WebFlux serialization (object vs array).

for a senior

Frames the choice as a type-level contract; notes backpressure and the Reactive Streams signal protocol.

for a principal

Discusses API design consequences of cardinality typing, streaming media types (SSE/NDJSON), and how cold semantics interact with retries/caching.

## Background **Project Reactor** is the reactive-streams library that **Spring WebFlux** (Spring's non-blocking web stack) uses as its core. A **reactive stream** is an asynchronous sequence of data with **backpressure** (the consumer can signal how much it can handle). Reactor provides exactly two concrete `Publisher` implementations you work with: - **`Mono<T>`** — a publisher that emits **at most one** item: either `0..1` `onNext` signals, then `onComplete`, or an `onError`. Think of it as a reactive `Optional<T>` or `CompletableFuture<T>`. - **`Flux<T>`** — a publisher that emits **0..N** items, then `onComplete` or `onError`. Think of it as a reactive `Stream<T>` or `List<T>` that arrives over time. Both implement the Reactive Streams `org.reactivestreams.Publisher<T>` interface, so both speak the same `onSubscribe / onNext / onError / onComplete` protocol. ## The signal contract A Mono's lifecycle is: `onSubscribe` → (optional single `onNext`) → `onComplete` **or** `onError`. It can never emit two values. A Flux's lifecycle is: `onSubscribe` → zero or more `onNext` → `onComplete` **or** `onError`. Terminal signals (`onComplete`/`onError`) happen exactly once and are mutually exclusive. ## Cold and lazy by default Both types are **cold publishers**: the pipeline you assemble is just a blueprint. **No work happens until something subscribes.** In WebFlux, the framework subscribes for you when it writes the HTTP response. This is why forgetting to return (or subscribe to) a Mono/Flux means the code silently never runs. ## In a Spring WebFlux controller ```java @GetMapping("/users/{id}") Mono<User> one(@PathVariable String id) { ... } // single JSON object @GetMapping("/users") Flux<User> all() { ... } // JSON array, or a stream ``` Returning `Mono<User>` serializes to one object; `Flux<User>` serializes to a JSON array by default, or to Server-Sent Events / NDJSON when the media type is streaming (`text/event-stream`, `application/x-ndjson`). ## When to use which - **Mono**: findById, save-and-return-one, count, exists, delete (returns `Mono<Void>`), any single-value async call (e.g. a `WebClient` call for one resource). - **Flux**: findAll, query results, streaming feeds, SSE, reading N rows from R2DBC, splitting a large payload. ## Common gotchas - **`Mono<Void>`** signals *completion only* (no value) — used for delete/side-effect endpoints. - Choosing Flux when the source is truly single (returning `Flux<User>` for a by-id lookup) is a code smell — the type lies about cardinality. - Both are **immutable**: operators return new instances; `flux.map(...)` doesn't mutate `flux`. - Neither runs on subscribe automatically outside WebFlux — in plain code you must call `.subscribe()` or block.

  • If a repository method logically returns one user but you declare it as Flux<User>, what's wrong?
    The type over-promises cardinality — callers must handle N items when there is at most one, forcing awkward next()/single() calls and obscuring the contract. Use Mono<User> so the type documents 0..1.
  • What does Mono<Void> represent and where is it used?
    A Mono that emits no value and only signals completion (or error). It's used for side-effecting operations like delete or a fire-and-forget save where the caller only needs to know it finished.

saying these in an interview costs you the question

  • Saying Flux is 'a list' and Mono is 'a single object' as if they were eager containers rather than lazy async streams
  • Claiming a Mono can emit multiple values
  • Thinking the pipeline executes as soon as it's constructed (before subscription)
  • Believing Mono<Void> emits a null value rather than just completing

context

open as a page

Walk through the main Reactor factory methods (just, empty, error, fromCallable, defer, fromIterable) and when you'd reach for each.

level: middleimportance: must knowfreq 75%

basics

~20 s

just wraps a ready value; empty completes with nothing; error emits a failure; fromCallable defers a synchronous/blocking call and captures its exceptions; defer builds a fresh publisher lazily per subscription; fromIterable turns a collection into a Flux.

open as a page

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

level: middleimportance: should knowfreq 60%

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.

open as a page

Explain why Mono.just(Instant.now()) gives every subscriber the same value while Mono.defer(() -> Mono.just(Instant.now())) does not. What does this reveal about assembly time vs subscription time?

level: seniorimportance: should knowfreq 55%

basics

~20 s

just captures its value once when the pipeline is built (assembly time), so all subscribers share it. defer runs its supplier on each subscription, producing a fresh value per subscriber. It shows Reactor separates building the pipeline from executing it.

open as a page

As a tech lead reviewing a WebFlux codebase, how do Mono/Flux cardinality and the eager-vs-lazy factory choices shape API contracts, error handling, and blocking-code integration? What patterns do you enforce?

level: principalimportance: should knowfreq 35%

basics

~20 s

Type return values by true cardinality (Mono for 0..1, Flux for 0..N) so contracts are honest. Ban Mono.just around blocking/side-effecting calls; require fromCallable+subscribeOn(boundedElastic) or defer for retriable/fresh work. Push errors into onError, avoid collectList on unbounded streams, and never block the event loop.

open as a page