Databases, Spring and microservices ยท 3. Spring Boot, lesson 9 of 9

Reactive Spring: WebFlux, Mono and Flux

Advanced3 min read@since 17Code runs on your Java 25
Explain it forThe essentials plus production detail and pitfalls.

Reactive programming handles data as asynchronous streams and never blocks a thread while waiting for I/O.

  • Reactive Streams defines Publisher, Subscriber and Subscription (also in the JDK as java.util.concurrent.Flow). Subscribers request items with request(n): that's backpressure.
  • Project Reactor provides Mono (0 or 1 item) and Flux (0 to many), with operators such as map, filter, flatMap and zip.
  • Nothing happens until something subscribes. Building a chain only describes the work.
  • Spring WebFlux is the reactive web framework: controllers return Mono or Flux, it runs on a small number of event-loop threads (Netty), and WebClient is its non-blocking HTTP client. R2DBC provides reactive database access.

When to use it: very high concurrency, streaming data (server-sent events), or gateways that mostly wait on other services. Since Java 21, virtual threads let ordinary blocking Spring MVC code scale to similar concurrency with simpler code, so for typical CRUD services Spring MVC remains the default choice.

Diagram

Example

Java
@RestController
@RequestMapping("/api/courses")
class CourseController {
    private final CourseRepository repo;        // a ReactiveCrudRepository (R2DBC)
    private final WebClient reviews;

    CourseController(CourseRepository repo, WebClient.Builder builder) {
        this.repo = repo;
        this.reviews = builder.baseUrl("https://reviews.internal").build();
    }

    @GetMapping
    Flux<Course> all() {
        return repo.findAll().filter(Course::published);       // streamed, not loaded all at once
    }

    @GetMapping("/{id}")
    Mono<CourseView> one(@PathVariable long id) {
        Mono<Course> course = repo.findById(id);
        Mono<Double> rating = reviews.get().uri("/ratings/{id}", id)
                .retrieve().bodyToMono(Double.class)
                .onErrorReturn(0.0);                            // degrade gracefully
        return Mono.zip(course, rating, CourseView::new);       // both calls run concurrently
    }
}

Common mistake

Mixing blocking calls (JDBC, RestTemplate, block()) into WebFlux code. It throws away the benefit and can freeze the server under load.

Under the hood

Never block inside a reactive pipeline: calling block(), a JDBC driver or Thread.sleep on an event-loop thread stalls every request that thread serves. Debugging is harder because stack traces show the framework, not your call chain (Reactor's checkpoint() and Hooks help). Reactive code pays off when the whole path is non-blocking, from the controller through the HTTP client to the database driver.

Check yourself

What happens when you build a Flux chain but nobody subscribes?

How this connects

Where this leads

You've reached the end of this thread. Try a learning path for what's next.

Was this lesson helpful?

Finished reading? Mark it complete to track your progress.