As an architect, how would you decide among Tomcat, Jetty, Undertow, and Netty for a Spring Boot service?
answer
- model first: MVC->servlet container, WebFlux->Netty
- Tomcat default/mature; Undertow lean; Jetty WebSockets
- benchmark YOUR workload, not blog numbers
- reactive = whole-codebase commitment
- weigh footprint, CVEs, ops familiarity
basics
~20 sFirst decide the programming model: blocking MVC -> a servlet container (Tomcat/Jetty/Undertow); non-blocking WebFlux -> Netty. Among servlet containers, keep Tomcat unless you have a measured reason (footprint, throughput, WebSockets, ops standardization) to pick Undertow or Jetty.
solid answer
~40 sThe primary axis is the programming model, driven by WebApplicationType. Blocking Spring MVC needs a servlet container — Tomcat (default, most mature), Jetty (light, strong for WebSockets/long-lived connections), or Undertow (JBoss, low footprint, good throughput). Non-blocking WebFlux fits Netty's event-loop model best, so Netty is the default there. Beyond that, keep the default unless you have evidence: benchmark under your real workload rather than trusting synthetic numbers, since results are workload-dependent and versions move. Secondary factors: memory footprint (Undertow/Netty tend lower), thread-model fit, HTTP/2 and WebSocket needs, security-patch cadence and CVE history, operational familiarity and existing tooling, and container-specific config surface. Don't adopt reactive/Netty just for throughput — it forces a non-blocking codebase end-to-end. Choose the simplest option that meets SLAs.
go deeper
Know Tomcat is the safe default and the others are alternatives.
Separate the model choice (MVC vs WebFlux) from the container choice among servlet servers.
List concrete factors (footprint, connection model, HTTP/2, CVEs) and note WebFlux commits the whole codebase.
Give a full decision framework, insist on workload-specific benchmarking, and weigh ops/security/cost alongside performance.
This is an architecture judgment question; interviewers want a **decision framework**, not a claim that server X is fastest. **Step 1 — Programming model first (the dominant decision).** This maps to `WebApplicationType`: - **Blocking / Spring MVC (SERVLET):** thread-per-request, simple mental model, huge ecosystem (blocking JDBC, servlet filters). Runs on a **servlet container**: Tomcat, Jetty, or Undertow. - **Non-blocking / Spring WebFlux (REACTIVE):** event-loop, high concurrency with few threads, requires non-blocking all the way down (R2DBC, reactive clients) or you lose the benefit. Default server is **Netty**. Getting this axis right matters far more than picking between servlet containers — reactive is a whole-codebase commitment, not a server toggle. **Step 2 — Within the servlet stack, choose the container:** - **Tomcat (default):** most battle-tested, best documented, widest operational familiarity, active security response. The safe choice; keep it unless proven otherwise. - **Undertow (JBoss/WildFly):** lightweight, non-blocking core (XNIO), often the smallest memory footprint and strong throughput; fewer people know its tuning knobs (`server.undertow.threads.io/worker`, buffer settings). - **Jetty:** long history of embeddability, popular for WebSocket-heavy and long-lived-connection workloads, flexible. **Step 3 — Netty** is really a *reactive* choice; you pick it by choosing WebFlux, not by swapping a servlet container. (WebFlux *can* run on Tomcat/Jetty/Undertow via Servlet 3.1+ non-blocking I/O, but Netty is the natural default.) **Decision factors to weigh (and verbalize):** 1. **Throughput/latency** — workload-dependent; **benchmark with your real traffic** (payload sizes, keep-alive, TLS, DB latency). Synthetic "X req/s" numbers age fast and mislead. 2. **Memory footprint** — Undertow and Netty typically lean; relevant for dense/containerized/serverless deploys where RAM = cost. 3. **Concurrency model fit** — many slow/long-lived connections (SSE, WebSocket, streaming) favor non-blocking (Undertow/Netty/Jetty); classic request/response with blocking DB is perfectly fine on Tomcat. 4. **Protocol needs** — HTTP/2, WebSocket, TLS/ALPN specifics differ per container. 5. **Security & maintenance** — CVE history and patch cadence; who ships fixes fast; your ability to upgrade. 6. **Operational familiarity** — the team's existing dashboards, access-log formats, tuning experience; a "faster" server nobody can operate is a liability. 7. **Config surface** — properties are container-scoped (`server.tomcat.*`, `server.jetty.*`, `server.undertow.*`); switching means re-tuning. **Anti-patterns to call out:** - Adopting WebFlux/Netty purely for benchmark throughput, then blocking on JDBC inside reactive types — worst of both worlds. - Swapping the default container based on a blog benchmark rather than measuring your own workload. - Ignoring the ops cost of a less-familiar server. **Bottom line to state:** default to Tomcat + MVC for typical services; move to reactive/Netty only when you genuinely need massive concurrency with non-blocking I/O end to end; pick Undertow/Jetty over Tomcat only for a specific, measured reason (footprint, connection model, standardization). Choose the simplest thing that meets your SLAs.
- A team wants to switch to WebFlux/Netty purely because a benchmark shows higher throughput. What do you advise?Caution. Reactive only pays off if the whole path is non-blocking (reactive DB/clients); blocking JDBC inside Mono/Flux negates it and adds complexity. Validate with a load test on real workload, and weigh the codebase and debugging cost, not just synthetic numbers.
- When would Undertow be a better pick than Tomcat?When memory footprint matters (dense/containerized deploys where RAM drives cost) or for workloads with many long-lived/non-blocking connections, provided the team can operate and tune it. Otherwise Tomcat's maturity and familiarity usually win.
saying these in an interview costs you the question
- Claiming one server is universally 'the fastest'
- Choosing Netty/WebFlux for throughput while keeping blocking JDBC
- Deciding on a blog benchmark instead of measuring your own workload
- Ignoring operational familiarity and security-patch cadence
- Treating Netty as just another servlet-container swap