skip to content

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