Skip to content

Spring Boot 3: Building Reactive WebFlux Microservices

CoreConceptAugust 3, 20269 min read

Traditional Java web applications rely on synchronous, blocking I/O models powered by the Servlet API (Tomcat, Jetty). Under high-concurrency workloads, dedicating a dedicated OS worker thread to every incoming HTTP request creates severe memory overhead (~1MB per thread) and thread context-switching bottlenecks when waiting for slow downstream database or microservice I/O calls.

Spring WebFlux brings non-blocking reactive programming to the Spring Boot ecosystem. Powered by Project Reactor and Netty, Spring WebFlux handles high-throughput asynchronous workloads using a small, fixed thread pool. This guide compares thread-per-request models against event loops, breaks down Mono and Flux reactive types, explores non-blocking database access with R2DBC, and demonstrates backpressure control.

Spring Boot WebFlux reactive architecture components and non-blocking event loop
Spring Boot WebFlux reactive architecture components and non-blocking event loop

Mental Model: Thread-per-Request (Servlet) vs Non-Blocking Event Loops (Netty)

Evaluating reactive web architecture requires contrasting traditional blocking Servlet execution against non-blocking event-driven runtimes.

In Spring MVC (Tomcat), each HTTP request consumes an active thread. If an API request waits 200ms for a SQL database query, that thread sits idle in a blocked state, unavailable to process other incoming HTTP connections.

In Spring WebFlux (Netty), a small event loop thread pool (typically equal to the number of CPU cores) handles all incoming TCP connections using I/O multiplexing (epoll/kqueue). When an API request issues an asynchronous I/O operation, the event loop registers an event handler and immediately processes other incoming requests. Once the database I/O completes, the event loop receives a completion signal and streams the response back to the client. For microservice architectural comparisons, review implementing cqrs and event sourcing in dotnet and building high throughput apis go gin framework.

Spring WebFlux non-blocking request lifecycle from Netty event loop to R2DBC query and response stream
Spring WebFlux non-blocking request lifecycle from Netty event loop to R2DBC query and response stream

Quick reference

  • Spring MVC allocates one dedicated thread per HTTP request, causing RAM exhaustion under load.
  • Spring WebFlux uses Netty event loops to handle 10,000+ connections with a small thread pool.
  • Non-blocking I/O multiplexing prevents OS threads from idling during database calls.
  • Consumes significantly less heap memory (~50KB per active connection vs ~1MB in Servlet engines).
  • Ideal for I/O-intensive gateway microservices, streaming endpoints, and WebSockets.

Remember this

Switch to Spring WebFlux event loops to process thousands of concurrent I/O requests with minimal CPU threads.

Project Reactor Primitives: Asynchronous Mono & Flux Data Streams

Spring WebFlux builds on Project Reactor, a Reactive Streams specification library in Java for composing asynchronous event streams.

Project Reactor introduces two primary reactive publishers: 1. Mono<T>: Emits 0 or 1 asynchronous item (Mono<Order>), making it the reactive equivalent of CompletableFuture<T> or a single HTTP/JSON object response. 2. Flux<T>: Emits 0 to N asynchronous items (Flux<Transaction>), representing continuous data streams or Server-Sent Event (SSE) payloads.

Reactive streams follow a lazy execution model: Nothing happens until you subscribe. Spring WebFlux handles subscription management automatically at the framework boundary, transforming reactive publisher emissions into HTTP response streams.

Quick reference

  • Mono<T> represents an asynchronous publisher emitting 0 or 1 item.
  • Flux<T> represents a continuous stream emitting 0 to N items over time.
  • Reactive streams execute lazily — execution starts only when a subscriber subscribes.
  • Use functional operators (map, flatMap, filter, zip) to transform and combine streams.
  • Spring WebFlux serializes Mono and Flux publishers to JSON or SSE streams transparently.

Remember this

Model asynchronous single items as Mono and continuous data streams as Flux in Spring WebFlux.

Reactive Relational Database Access using R2DBC

Using reactive HTTP controllers with traditional JDBC ORM drivers (such as Hibernate or JPA) ruins non-blocking guarantees. JDBC calls block the underlying thread during database queries, defeating WebFlux's event loop architecture.

R2DBC (Reactive Relational Database Connectivity) brings non-blocking SQL drivers to PostgreSQL, MySQL, and SQL Server. R2DBC drivers return Mono and Flux publishers directly from database queries.

Spring Data R2DBC provides ReactiveCrudRepository interfaces (Mono<Order> findById(Long id)). Because R2DBC operates non-blockingly, a single database connection pool can serve thousands of concurrent queries without thread starvation.

Spring WebFlux non-blocking request lifecycle from Netty event loop to R2DBC query and response stream
Spring WebFlux non-blocking request lifecycle from Netty event loop to R2DBC query and response stream

Quick reference

  • Traditional JDBC drivers block threads, destroying WebFlux non-blocking execution gains.
  • R2DBC provides native non-blocking SQL database drivers for PostgreSQL and MySQL.
  • Spring Data R2DBC repositories return reactive Mono and Flux objects natively.
  • Dramatically reduces required database connection pool sizes under high concurrency.
  • Supports reactive database transactions using TransactionalOperator.

Remember this

Use R2DBC reactive drivers instead of blocking JDBC to maintain end-to-end non-blocking database queries.

Backpressure Control and Resilient Reactive Operators

When a fast publisher streams data faster than a slow subscriber can consume it, memory buffers overflow, causing OutOfMemoryError crashes. Reactive Streams solve this using Backpressure.

Backpressure allows consumers to signal exact demand to publishers (request(n)), controlling data emission rates. Flux streams support strategies such as onBackpressureBuffer(), onBackpressureDrop(), or onBackpressureLatest() to handle traffic surges gracefully.

Combine backpressure with Reactor's built-in resilience operators: .timeout(Duration.ofSeconds(2)) cancels slow queries, .retryWhen(Retry.backoff(3, Duration.ofMillis(100))) handles transient network blips, and .onErrorResume(fallback) provides instant graceful degradation.

Quick reference

  • Backpressure allows subscribers to request specific item batch sizes (request(n)) from publishers.
  • Prevents fast database or upstream producers from overwhelming slow downstream consumers.
  • onBackpressureDrop and onBackpressureLatest discard excess emissions during load spikes.
  • Reactor resilience operators (timeout, retryWhen, onErrorResume) handle failures inline.
  • Integrates with Resilience4j for reactive circuit breaker state machine enforcement.

Remember this

Apply reactive backpressure strategies and Reactor resilience operators to prevent memory overflow under heavy load.

Key takeaway

To test Spring WebFlux performance, execute ab -n 10000 -c 500 http://localhost:8080/api/v1/stream. Verify that Netty handles 500 concurrent connections using under 20 JVM threads.

Share:

Related Articles

Traditional perimeter-based security ('Castle and Moat') assumes that all traffic inside a private network or Kubernetes

Read

Decoupling microservices using Event-Driven Architecture (EDA) requires choosing an asynchronous messaging backbone. Eng

Read

In high-concurrency microservices architectures, preventing race conditions when multiple stateless worker instances acc

Read

Keep learning

Follow a structured path or browse all courses to go deeper.