Start Learning
Javaneer
Back to stage
Module 6·Reactive & Messaging

Reactive Programming & Reactor

Why non-blocking matters, and the two publisher types at the heart of it: Mono (0-1) and Flux (0-N).

18 min readAdvanced
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 waiter who doesn't stand at the kitchen

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 arrives

Watch values flow through an operator and become a new stream:

A Flux flowing through an operatorValues arrive over time on a stream; an operator transforms them into a new stream. Pick an operator and run it.
source
1234
.map(x -> x * 10)
result
10203040
Flux.just(1, 2, 3, 4).map(x -> x * 10)

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.

Mono or Flux?

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.
Was this lesson helpful?
Edit this page on GitHub