Reactive Programming & Reactor
Why non-blocking matters, and the two publisher types at the heart of it: Mono (0-1) and Flux (0-N).
On this page
Everything you've built so far is blocking: a thread handles a request, and when it calls the database or another service, that thread waits - doing nothing - until the answer comes back. That's fine at moderate load. But when a service must juggle tens of thousands of slow, concurrent connections, a thread-per-request model runs out of threads. Reactive programming is the answer: never block a thread; react to data as it arrives.
Blocking vs non-blocking
- Blocking: thread calls the DB, then sleeps until the result returns. One thread is tied up per in-flight request.
- Non-blocking: thread fires off the call and is immediately free to do other work; when the result is ready, a callback resumes processing. A handful of threads serve enormous concurrency.
A blocking waiter takes your order, walks to the kitchen, and stands there until your meal is cooked before serving anyone else - one waiter per table. A reactive waiter drops the order at the kitchen and immediately serves other tables; when a meal is ready, the kitchen signals and the waiter delivers it. The same few waiters handle a packed restaurant, because none of them ever just stands and waits.
Mono and Flux
Project Reactor (the library under Spring's reactive stack) gives you two publishers - async sequences you can transform:
Mono<T>— 0 or 1 value (a single user, a save result, an empty response).Flux<T>— 0 to N values over time (a stream of books, live price ticks).
They're lazy: nothing runs until something subscribes. Until then you're just
building a pipeline of operators - map, filter, flatMap - exactly like the Stream API,
but asynchronous and over time.
Flux<Book> books = bookRepository.findAll(); // nothing has run yet
books
.filter(b -> b.getCopiesAvailable() > 0) // keep available
.map(Book::getTitle) // to titles
.subscribe(System.out::println); // NOW it runs, as data arrivesWatch values flow through an operator and become a new stream:
Reactive is a means, not a default
Reactive shines under massive I/O-bound concurrency. It also makes code harder to write, read, and debug, and one accidental blocking call poisons the whole chain. For most CRUD apps - BookVault included - the blocking model with virtual threads (next module) is simpler and plenty. Reach for reactive when you truly need to handle huge concurrency on few threads.
For each BookVault operation, pick Mono or Flux and why: (1) find one book by ISBN,
(2) list all available books, (3) count the total books, (4) stream live "book borrowed"
events to a dashboard.
What do Mono and Flux represent in Project Reactor?
Key takeaways
- Blocking code ties up a thread while it waits on I/O; reactive code never blocks, so a few threads serve huge concurrency.
- Project Reactor's publishers are Mono (0-1 value) and Flux (0-N values over time).
- They're lazy pipelines of operators (map/filter/flatMap) that run only when subscribed - like an async Stream API.
- Reactive is powerful under massive I/O concurrency but harder to write and debug, and a single blocking call breaks it.
- For ordinary CRUD apps, blocking code (with virtual threads) is simpler; reach for reactive when the load truly demands it.