Spring Cloud Gateway ships both a reactive (WebFlux/Netty) server and a servlet (Spring MVC) server. As an architect, how do you decide which to run, and what does the reactive stack actually buy you?
answer
- same Route model, two runtimes: WebFlux/Netty vs servlet/RestClient
- reactive buys: concurrency-per-thread + streaming/backpressure + long-lived conns
- reactive costs: async model + no-blocking rule + ecosystem friction
- MVC buys: blocking libs + simpler debug + Loom virtual threads
- don't pick reactive if your logic is inherently blocking
basics
~20 sThe reactive gateway scales huge concurrency with few threads and streams bodies with backpressure — ideal for high fan-out, slow backends, or streaming. The MVC gateway uses familiar blocking code and libraries. Choose reactive for scale/streaming; MVC when the team relies on blocking stacks and concurrency is moderate.
solid answer
~50 sBoth variants offer the same routing/predicate/filter model; the difference is the runtime. The **reactive** server (Spring Cloud Gateway Server WebFlux on Netty) uses a small event-loop pool to carry very high concurrent connection counts cheaply, and streams request/response bodies with end-to-end backpressure — a strong fit for large fan-out, many slow or long-lived downstreams, streaming/SSE, and high connection concurrency. Its cost is a harder async programming model and the hard rule that nothing may block the event loop; blocking libraries (JDBC, blocking auth SDKs) must be offloaded or replaced. The **MVC** server (Spring Cloud Gateway Server MVC on the servlet stack, backed by `RestClient`) lets teams write ordinary blocking filters and reuse blocking libraries with thread-per-request simplicity, at the cost of a thread per in-flight request. Decide by workload: reactive when concurrency/streaming dominates and you can stay non-blocking; MVC when concurrency is moderate and the team's ecosystem is blocking.
go deeper
Not expected; may only know the reactive gateway exists.
Should know reactive scales concurrency with few threads and there's a servlet alternative.
Should articulate the streaming/backpressure and no-blocking tradeoffs and pick per workload.
Should give a full decision framework, weigh virtual threads, warn against reactive-when-blocking anti-pattern, and address operational/observability parity.
## Same façade, two engines Spring Cloud Gateway exposes one mental model — **Routes** (a route = id + URI + predicates + filters) — over two server runtimes: - **Server WebFlux (reactive):** the classic gateway, on Spring WebFlux + Project Reactor + **Netty**. Proxying uses reactor-netty's non-blocking `HttpClient`. - **Server MVC (servlet):** a newer variant on the Spring MVC servlet stack (WebMvc.fn), proxying with the blocking `RestClient`. Filters/predicates are written in a blocking style. (These have been renamed/repackaged across Spring Cloud versions, but the two-runtime split is the durable idea.) ## What reactive actually buys you 1. **Connection-concurrency efficiency:** an event loop (~one thread per core) multiplexes thousands of waiting proxied requests. Memory/threads scale with *active* work, not in-flight count. For a fan-out gateway fronting many/slow backends, this is dramatically cheaper than thread-per-request. 2. **End-to-end streaming + backpressure:** bodies flow as `Flux<DataBuffer>`; reactor-netty ties Reactive Streams demand to TCP flow control, so large uploads/downloads and SSE/streaming APIs relay in constant memory and a slow side throttles the fast side. 3. **Long-lived/streaming protocols:** WebSockets, SSE, and many idle-but-open connections are cheap when a connection doesn't pin a thread. ## What it costs 1. **Programming model:** everything is `Mono`/`Flux`; debugging, stack traces, and context propagation are harder. 2. **No-blocking rule:** the event loop must never block. Blocking JDBC/JPA, blocking HTTP clients, blocking auth/crypto SDKs, heavy CPU — all must be replaced (WebClient, R2DBC) or offloaded to `Schedulers.boundedElastic()`. A single accidental block can cause cliff-edge throughput collapse. Guard with BlockHound. 3. **Ecosystem friction:** libraries the team depends on may be blocking-only. ## What MVC buys you 1. **Familiarity & ecosystem:** ordinary blocking code, blocking libraries reused as-is, simpler debugging. 2. **Adequate at moderate concurrency:** with modern hardware and virtual threads (Project Loom on the servlet stack), thread-per-request handles far more concurrency than before, narrowing the reactive advantage for many workloads. 3. **No event-loop footgun:** a blocking call costs one request, not the whole server. ## Decision framework - **Choose reactive when:** very high connection concurrency, large fan-out, slow/long-tail downstreams, streaming/SSE/WebSocket relaying, big body streaming, and the team can commit to a non-blocking codebase. - **Choose MVC when:** moderate concurrency, the filter logic must call blocking libraries you can't replace, the team lacks reactive expertise, or virtual threads already give you the concurrency you need with simpler code. - **Neutral factors:** routing/predicate/filter capabilities are largely equivalent; both integrate with discovery, resilience (circuit breakers), and rate limiting. ## Gotchas at the architecture level - **Don't pick reactive for prestige.** If your gateway logic is inherently blocking (heavy DB lookups per request) and you'll just wrap everything in `boundedElastic`, you've paid the complexity tax and thrown away the benefit — MVC (or reactive with R2DBC) is more honest. - **Virtual threads shift the calculus:** Loom makes blocking-style code scale on the servlet stack, so the historical 'reactive for concurrency' argument is weaker for plain fan-out; reactive's clearest remaining edge is true streaming/backpressure and very-high idle-connection counts. - **Operational parity:** ensure your observability, tracing (context propagation across scheduler hops in reactive), and load-testing reflect the chosen model. ## Bottom line Reactive is the right default for a high-scale, streaming, fan-out edge where you can stay non-blocking. MVC is a legitimate, simpler choice when concurrency is moderate or your logic must block — increasingly viable with virtual threads.
- How do virtual threads (Project Loom) change the reactive-vs-MVC gateway decision?Virtual threads let blocking-style code on the servlet stack scale to very high concurrency cheaply, weakening the classic 'reactive for concurrency' argument for plain fan-out. Reactive's clearest remaining advantages become true end-to-end streaming/backpressure and very high counts of idle long-lived connections, rather than raw concurrent-request count.
- Your team must call a blocking legacy service for every request in the gateway. Which stack, and why?Lean toward the MVC gateway (or reactive with the blocking call offloaded to boundedElastic, or better, a non-blocking client). If nearly every request blocks and you'd wrap everything in boundedElastic, you pay reactive's complexity while losing its benefit — MVC's thread-per-request (ideally with virtual threads) is simpler and honest.
saying these in an interview costs you the question
- Claiming reactive is strictly better and MVC gateway shouldn't exist
- Choosing reactive while planning to wrap all logic in boundedElastic (defeats the purpose)
- Ignoring that virtual threads narrow the concurrency gap
- Assuming the two variants differ in routing/predicate features (they're largely equivalent)