Backend Development 17 min read

Why Spring WebFlux? A Deep Dive into Reactive, Non‑Blocking Backend Development

This article explains the motivations behind Spring WebFlux, introduces reactive programming concepts, compares annotated controllers with functional endpoints, discusses when to choose WebFlux over Spring MVC, and provides complete code examples for building a reactive CRUD service with Spring Boot.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Why Spring WebFlux? A Deep Dive into Reactive, Non‑Blocking Backend Development

Overview

Spring WebFlux was created to provide a non‑blocking web stack that can handle concurrency with a small number of threads and scale using fewer hardware resources. While Servlet 3.1 offers non‑blocking I/O, it forces compromises elsewhere in the Servlet API, prompting the need for a new, fully reactive public API that works on any non‑blocking runtime such as Netty.

Another driver is functional programming. Java 5 introduced annotations, enabling annotated REST controllers and unit tests; Java 8 added lambda expressions, opening the door for a functional API in Java. This enables declarative composition of asynchronous logic using CompletableFuture, ReactiveX, and, at the programming‑model level, Java 8 support for functional endpoints and annotated controllers in Spring WebFlux.

What is reactive programming? It is a model built around reacting to changes—network I/O events, UI events, etc. Non‑blocking code is reactive because it reacts when data becomes available instead of blocking. A crucial mechanism is back‑pressure, which in synchronous code is implicit (the caller waits), but in non‑blocking code the producer must be throttled to avoid overwhelming the consumer.

Reactive Streams is a small specification (adopted in Java 9) that defines asynchronous components with back‑pressure. It lets a subscriber control the rate at which a publisher emits data, enabling safe interaction between data repositories and HTTP servers.

Reactor is the preferred reactive library for Spring WebFlux. It provides

Mono

(0..1) and

Flux

(0..N) types and a rich set of operators aligned with the ReactiveX vocabulary. Reactor implements the Reactive Streams spec, so all its operators support non‑blocking back‑pressure and are tightly integrated with Spring.

Programming Model

Spring WebFlux builds on the core Spring Web module (HTTP abstraction, server adapters, codecs, etc.) but with a non‑blocking contract.

Two programming models are offered:

Annotated Controllers: The same annotations used in Spring MVC, supporting reactive return types (Reactor, RxJava). The main difference is that WebFlux also allows reactive

@RequestBody

parameters.

Functional Endpoints: A lightweight, lambda‑based model where routing and handling are expressed with functional utilities. Unlike annotated controllers, the application code explicitly handles the request from start to finish.

Applicability

How to choose between Spring MVC and WebFlux? They can coexist, and the diagram (shown below) illustrates their overlap and unique features.

Spring MVC vs WebFlux
Spring MVC vs WebFlux

Guidelines:

If you already have a well‑functioning Spring MVC application, there is no need to switch; the imperative model is simpler to write, understand, and debug.

If you need a non‑blocking stack, WebFlux offers the same execution‑model benefits as other reactive servers and lets you pick a server (Netty, Tomcat, Jetty, Undertow) and a reactive library (Reactor, RxJava, etc.).

For lightweight functional web frameworks targeting Java 8 lambdas or Kotlin, the functional endpoint model is a good fit for small services or micro‑services.

In a micro‑service architecture you can mix Spring MVC and WebFlux controllers or functional endpoints; both share the same annotation‑based programming model, easing knowledge reuse.

Check your dependencies: if you rely heavily on blocking APIs (JPA, JDBC), Spring MVC is usually the safer choice. Reactive stacks can still call blocking code on dedicated threads, but you won’t fully exploit non‑blocking benefits.

If you have an existing Spring MVC app that calls remote services, consider using the reactive

WebClient

. Controllers can return reactive types, and the benefits grow with higher latency or inter‑service dependencies.

Be aware of the steep learning curve when moving to non‑blocking, functional, and declarative programming. Starting with

WebClient

and measuring incremental gains is a pragmatic approach.

Application Services

Spring WebFlux runs on Tomcat, Jetty, Servlet 3.1+ containers, as well as non‑Servlet runtimes like Netty and Undertow. All servers expose a low‑level generic API, enabling higher‑level programming models to be portable.

Spring Boot provides a WebFlux starter that auto‑configures the runtime. By default it uses Netty, but you can switch to Tomcat, Jetty, or Undertow by changing Maven/Gradle dependencies.

Performance

Reactive, non‑blocking code does not automatically make an application faster; it can improve scalability by using fewer threads and less memory, especially under high latency or unpredictable I/O. The real benefit appears when the system can keep a small, fixed thread pool busy while handling many concurrent requests.

Thread Model

In Spring MVC (or any servlet app) the assumption is that the thread may block, so containers allocate a large thread pool to absorb potential blocking.

In Spring WebFlux the assumption is that the application does not block, so a small, fixed‑size event‑loop thread pool handles requests.

Calling blocking APIs: Reactor and RxJava provide

publishOn

to shift work to another thread pool, but blocking calls are generally discouraged.

Mutable state: Reactive pipelines are immutable, reducing the need for explicit synchronization.

When running a typical WebFlux server you will see one event‑loop thread plus a few request‑handling threads (often matching CPU cores). Servlet containers may still start with more threads to support both blocking and non‑blocking I/O.

The reactive

WebClient

also runs on an event‑loop, sharing resources with the server when using Reactor Netty.

Reactor and RxJava expose schedulers (e.g.,

parallel

,

elastic

) that define concurrency strategies; seeing such threads indicates that code is using a specific scheduler.

Introducing Dependencies

<code>&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-webflux&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;!-- Reactive R2DBC support --&gt;
&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-data-r2dbc&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;!-- MySQL R2DBC driver --&gt;
&lt;dependency&gt;
  &lt;groupId&gt;dev.miku&lt;/groupId&gt;
  &lt;artifactId&gt;r2dbc-mysql&lt;/artifactId&gt;
&lt;/dependency&gt;</code>

Related Configuration

<code>spring:
  r2dbc:
    # URL uses r2dbc prefix instead of jdbc
    url: r2dbc:mysql://localhost:3306/testjpa
    username: root
    password: 123123</code>

PO Definition

<code>@Table("reactive_users")
public class Users implements Serializable {
    @Id
    private Long id;
    private Integer age;
    private String name;
}</code>

DAO Layer Definition

<code>// ReactiveCrudRepository replaces JpaRepository in reactive code
public interface UsersRepository extends ReactiveCrudRepository<Users, Long>, ReactiveSortingRepository<Users, Long> {
    Mono<Users> findByName(String name);
}</code>

Service Layer Definition

<code>@Service
public class UsersService {
    @Resource
    private UsersRepository usersRepository;

    @Transactional
    public Mono<Users> save(Users users) {
        return usersRepository.save(users);
    }

    public Mono<Users> getUsers(Long id) {
        return usersRepository.findById(id);
    }

    public Flux<Users> list() {
        return usersRepository.findAll();
    }

    public Mono<Users> getUsersByName(String name) {
        return usersRepository.findByName(name);
    }
}</code>

Controller Interface Definition

<code>@RestController
@RequestMapping("/users")
public class UsersController {
    @Resource
    private UsersService usersService;

    @PostMapping("/save")
    public Mono<Long> save(@RequestBody Users user) {
        return usersService.save(user)
                .flatMap(u -> Mono.just(u.getId()));
    }

    @GetMapping("/{id}")
    public Mono<Users> getUsers(@PathVariable("id") Long id) {
        return usersService.getUsers(id);
    }

    @GetMapping("/lists")
    public Flux<Users> list() {
        return usersService.list();
    }

    @GetMapping("/name")
    public Mono<Users> name(String name) {
        return usersService.getUsersByName(name);
    }
}</code>

The controller definitions look similar to traditional Spring MVC, but they return

Mono

(single value) or

Flux

(collection) objects.

For more details on

Mono

and

Flux

operations, see the referenced article on Reactor basics.

JavaBackend DevelopmentReactive ProgrammingSpring BootNon-blocking I/OSpring WebFlux
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.