Skip to content
C Codeloom
Java

Spring MVC vs Spring WebFlux

Compare Spring's two web stacks: thread-per-request Spring MVC versus reactive non-blocking WebFlux, and how to choose between them.

·3 min read · By Codeloom
Intermediate 10 min read

What you'll learn

  • Thread-per-request model
  • Reactive Streams model
  • Backpressure
  • Choosing between stacks
  • Virtual threads impact

Prerequisites

  • Basic familiarity with Spring Boot

What and Why

Spring offers two parallel web stacks. Spring MVC is the traditional servlet-based model: one thread handles one request from start to finish. Spring WebFlux is built on Project Reactor and runs on Netty (by default), processing requests with a small pool of event-loop threads that never block.

Choosing between them affects throughput, latency, library compatibility, and developer ergonomics.

Mental Model

Spring MVC is a bank with many tellers; each customer monopolizes a teller until done. WebFlux is a bank with a few tellers who hand off the slow paperwork to backroom staff (I/O) and immediately serve the next customer.

Spring MVC:                 Spring WebFlux:
Req1 --> Thread1 --> DB     Req1 --\
Req2 --> Thread2 --> DB     Req2 ---|--> EventLoop --> async I/O
Req3 --> Thread3 --> DB     Req3 --/                    callback
Two concurrency models

Hands-on Example

Spring MVC controller:

@RestController
public class UserController {
  private final UserRepository repo;

  @GetMapping("/users/{id}")
  public User get(@PathVariable Long id) {
    return repo.findById(id).orElseThrow();
  }
}

WebFlux equivalent using Mono:

@RestController
public class UserController {
  private final ReactiveUserRepository repo;

  @GetMapping("/users/{id}")
  public Mono<User> get(@PathVariable Long id) {
    return repo.findById(id)
      .switchIfEmpty(Mono.error(new NotFoundException()));
  }
}

WebFlux returns a Mono<User> (0 or 1) or Flux<User> (0..N). The framework subscribes to it and writes data as it becomes available.

Common Pitfalls

Blocking calls in WebFlux. A single Thread.sleep, a synchronous JDBC call, or any blocking I/O on the event loop kills throughput for all concurrent requests. Use Schedulers.boundedElastic() for unavoidable blocking work, or switch to R2DBC.

Assuming WebFlux is always faster. For most workloads with modest concurrency, MVC matches or beats WebFlux on latency. Reactive shines under very high concurrency with many slow downstream calls.

Library compatibility. Many JVM libraries (JDBC drivers, some auth providers, BlazeDS) are blocking. They work in MVC out of the box but require careful scheduling in WebFlux.

Stack trace pain. Reactive stack traces are notoriously hard to read. Enable Hooks.onOperatorDebug() in development, or use ReactorDebugAgent.init() in production for cleaner traces.

Backpressure ignored. If your producer is faster than your consumer, you need onBackpressureBuffer, onBackpressureDrop, or similar. Otherwise you get out-of-memory errors.

Practical Tips

  • Use MVC when: your team is new to reactive, your downstream is JDBC, traffic is moderate, and you value debuggability.
  • Use WebFlux when: you’re aggregating many slow I/O calls per request, building streaming endpoints (SSE, WebSockets), or hitting socket exhaustion with MVC.
  • With Java 21+ virtual threads, MVC’s main downside (one OS thread per request) largely disappears. Set spring.threads.virtual.enabled=true and reconsider whether you need WebFlux at all.
  • Don’t mix paradigms in one service if you can avoid it. The cognitive overhead of blocking-vs-reactive boundaries is significant.
  • Use WebClient even in MVC apps; RestTemplate is in maintenance mode.

Wrap-up

Spring MVC is the right default for the vast majority of services, especially with virtual threads now available. WebFlux earns its keep when you need very high concurrency, streaming, or aggregating many async sources. Choose based on the workload’s I/O profile and your team’s comfort, not on hype. The two stacks share too much ecosystem to make this a one-way door, but switching mid-project is still painful, so decide deliberately upfront.