Course topics

By WebNest Studio Engineering Team

Spring Framework Tutorial

Spring Reactive Programming Overview

Traditional Spring MVC follows a thread-per-request model: each incoming HTTP request occupies one thread for its entire lifetime, including the time spent blocked while waiting on a database query, a downstream HTTP call, or disk I/O. That model works well until concurrency climbs into the thousands, at which point you either run out of threads or pay a heavy cost in context switching and memory (each thread reserves its own stack).

Reactive programming addresses this by making I/O non-blocking and asynchronous end to end, so a small, fixed pool of threads can serve a very large number of concurrent requests. Spring's reactive stack, built on Project Reactor and exposed through Spring WebFlux, is Spring's answer to this problem — but it is not a universal upgrade over Spring MVC, and this lesson covers both what it buys you and what it costs.

Why Reactive Programming Exists

Reactive systems are designed around one central idea: never block a thread waiting for something slow to finish. Instead of a thread sitting idle during a database call, that thread is released back to a shared pool immediately, and a callback (or a reactive operator chain) resumes the work once the data arrives. This lets a handful of threads handle tens of thousands of concurrent connections, which matters most for I/O-heavy services — API gateways, streaming data pipelines, and systems that fan out to many downstream services per request. Reactive streams also support backpressure: a slow consumer can signal an upstream producer to slow down, rather than being overwhelmed by data it cannot process fast enough.

Mono and Flux in Project Reactor

Project Reactor is the reactive library Spring builds on, and it centers on two publisher types. A Mono<T> represents an asynchronous stream that emits at most one item (or an error, or completes with nothing) — conceptually similar to a CompletableFuture, but lazy and composable. A Flux<T> represents an asynchronous stream of zero to many items over time, like a reactive equivalent of a list or a stream of events. Neither type does any work until something subscribes to it; you build a pipeline of operators (map, filter, flatMap, and so on) declaratively, and the actual data only flows once a subscriber attaches, at which point the whole chain executes without blocking the calling thread.

  • Mono<User> — represents "eventually one User, or nothing, or an error," e.g. the result of a lookup by ID.
  • Flux<Order> — represents "a stream of Order objects arriving over time," e.g. results from a large query or a live event feed.
  • Operators like .map(), .filter(), and .flatMap() transform the stream without ever calling .get() or blocking to retrieve a value.

Reactive (WebFlux) vs Traditional Blocking (Spring MVC)

Spring MVC handlers return plain objects or blocking types, and the servlet container assigns one thread per request for its full duration; this is simple to reason about and debug, and most relational database drivers are blocking anyway (JDBC is inherently synchronous), which limits how much benefit reactive brings unless your whole stack — including the database driver — is non-blocking. Spring WebFlux, in contrast, runs on a small event-loop thread pool (via Netty by default), and handler methods return Mono or Flux; the framework subscribes to them and writes the response as data becomes available, without ever parking a thread on I/O.

When Reactive Is (and Is Not) Worth It

Reactive programming earns its complexity in genuinely high-concurrency, I/O-bound scenarios: gateways aggregating many downstream calls, streaming APIs, or services that need to scale to a very large number of simultaneous connections on limited hardware. It is usually not worth it for typical CRUD applications backed by a traditional relational database over blocking JDBC, where you would need R2DBC (a reactive database driver) to see any real benefit, and where the debugging difficulty of stack traces across asynchronous boundaries, combined with a smaller pool of developers fluent in reactive style, often outweighs the throughput gains. A common rule of thumb: default to Spring MVC, and reach for WebFlux only when you have measured a concrete concurrency bottleneck that non-blocking I/O would actually fix.

Example

A simple Mono and Flux pipeline built with Project Reactor operators (no blocking calls)

Java
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class ReactiveDemo {
    public static void main(String[] args) {
        Mono<String> mono = Mono.just("Alice")
                .map(name -> "Hello, " + name);
        mono.subscribe(System.out::println);

        Flux<Integer> flux = Flux.range(1, 5)
                .filter(n -> n % 2 == 0)
                .map(n -> n * 10);
        flux.subscribe(System.out::println);
    }
}
Output
Hello, Alice
20
40

Common Mistakes

  • Calling .block() on a Mono or Flux inside a WebFlux handler to "get the value out" — this defeats the entire purpose and can deadlock the small event-loop thread pool under load.
  • Adopting WebFlux for a standard CRUD app on a blocking JDBC driver, gaining none of the non-blocking benefit while paying the full complexity cost.
  • Assuming a Mono or Flux pipeline executes as soon as it is built — nothing happens until something subscribes to it.
  • Treating reactive code as a drop-in performance upgrade rather than a different concurrency model that requires non-blocking drivers throughout the stack to pay off.

Exercise

Using the lesson's A simple Mono and Flux pipeline built with Project Reactor operators (no blocking calls) example, create a small implementation or design sketch that demonstrates Reactive programming avoids blocking threads on I/O, letting a small thread pool serve very high concurrency, and supports backpressure between producer and consumer.. Add a test case or scenario that would catch Calling .block() on a Mono or Flux inside a WebFlux handler to "get the value out" — this defeats the entire purpose and can deadlock the small event-loop thread pool under load., and describe the expected result.

Show hints
  • Start with Why Reactive Programming Exists and compare your approach with A simple Mono and Flux pipeline built with Project Reactor operators (no blocking calls). Identify the decision or behavior the lesson recommends.
  • Walk through your scenario step by step. Confirm that it follows Reactive programming avoids blocking threads on I/O, letting a small thread pool serve very high concurrency, and supports backpressure between producer and consumer. and that it would catch the failure described by Calling .block() on a Mono or Flux inside a WebFlux handler to "get the value out" — this defeats the entire purpose and can deadlock the small event-loop thread pool under load..

Quick quiz

1. Which choice is a mistake to avoid in Spring Reactive Programming Overview?

  • A. Adopting WebFlux for a standard CRUD app on a blocking JDBC driver, gaining none of the non-blocking benefit while paying the full complexity cost.
  • B. Reactive programming avoids blocking threads on I/O, letting a small thread pool serve very high concurrency, and supports backpressure between producer and consumer.
  • C. Calling .block() on a Mono or Flux inside a WebFlux handler to "get the value out" — this defeats the entire purpose and can deadlock the small event-loop thread pool under load.
Show answer

C. Avoid this approach: Calling .block() on a Mono or Flux inside a WebFlux handler to "get the value out" — this defeats the entire purpose and can deadlock the small event-loop thread pool under load. The lesson recommends: Reactive programming avoids blocking threads on I/O, letting a small thread pool serve very high concurrency, and supports backpressure between producer and consumer.

Interview questions

A teammate is about to make this mistake: Calling .block() on a Mono or Flux inside a WebFlux handler to "get the value out" — this defeats the entire purpose and can deadlock the small event-loop thread pool under load. How would you diagnose and correct it using the lesson's guidance?

Show answer

Reactive programming avoids blocking threads on I/O, letting a small thread pool serve very high concurrency, and supports backpressure between producer and consumer. Apply this in Why Reactive Programming Exists; the example A simple Mono and Flux pipeline built with Project Reactor operators (no blocking calls) shows one way to do it. Avoid Calling .block() on a Mono or Flux inside a WebFlux handler to "get the value out" — this defeats the entire purpose and can deadlock the small event-loop thread pool under load. and explain how your implementation or design prevents that failure.

Key Points to Remember

  • Reactive programming avoids blocking threads on I/O, letting a small thread pool serve very high concurrency, and supports backpressure between producer and consumer.
  • Mono represents zero-or-one async result; Flux represents zero-to-many async results over time; both are lazy until subscribed.
  • Spring MVC is thread-per-request and blocking; Spring WebFlux runs on an event loop and is non-blocking end to end, typically via Netty.
  • Reactive is worth the complexity for high-concurrency, I/O-bound workloads with non-blocking drivers throughout — not a default upgrade for typical blocking, JDBC-backed applications.

Practice in the playground

Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.

Run the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

These examples need a JDK project with their libraries on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.

WebNest Studio

Written by

WebNest Studio Engineering Team

Practical lessons from developers working with Java, Python, React, databases and production systems.

Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering