Servlet Architecture Deep Dive

The Servlet API is the foundation under most Java web frameworks. Spring MVC, Jersey, JAX-RS, raw web applications — all built on Servlet at the bottom. Modern Java backends (Spring WebFlux, reactive frameworks) increasingly bypass Servlet, but Servlet-based stacks remain dominant.

This page covers what Servlet actually does and how the major frameworks sit on it.

The basics

A servlet is a Java class that handles HTTP requests:

@WebServlet("/api/orders/*")
public class OrderServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // handle GET
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // handle POST
    }
}

The container (Tomcat, Jetty, Undertow) manages servlet lifecycle and dispatches requests.

The request lifecycle

When an HTTP request arrives:

  1. Container receives request: TCP connection, HTTP parsing, request object construction
  2. Servlet matching: container finds the servlet whose mapping matches the URL
  3. Filter chain: any filters configured for this URL run in order
  4. Servlet method invocation: doGet, doPost, etc.
  5. Response writing: servlet writes to HttpServletResponse
  6. Filter chain (reverse): filters' post-processing runs in reverse order
  7. Container sends response: HTTP framing, TCP write

Each request runs on a thread from the container's thread pool. With virtual threads (Java 21+), this can be a virtual thread.

Filters

Filters intercept requests:

@WebFilter("/*")
public class LoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {

        long start = System.nanoTime();
        try {
            chain.doFilter(req, resp);
        } finally {
            long duration = System.nanoTime() - start;
            log.info("Request took {}ns", duration);
        }
    }
}

Common filter use cases:

Spring Security, Wikantik's McpAccessFilter, etc. — all servlet filters.

Listeners

Listeners react to lifecycle events:

Used for one-time initialization, cleanup, observability. Spring Boot's ApplicationContext lifecycle hooks ultimately bottom out in servlet listeners.

Servlet context

A shared map across all servlets in the same application:

ServletContext ctx = req.getServletContext();
ctx.setAttribute("startTime", Instant.now());

Used for application-wide configuration. In Spring Boot, configuration is more typically Spring beans, but the servlet context still exists underneath.

Sessions

HttpSession session = req.getSession();
session.setAttribute("userId", userId);

Servlet sessions are per-user, per-application state. In modern stateless designs, sessions are increasingly replaced by JWTs or other token-based auth. Where sessions persist, they're typically backed by a session store (Redis, etc.) for clustering.

How Spring MVC sits on top

Spring's DispatcherServlet is a servlet. When configured (Spring Boot does this automatically), it's mapped at /* (or another configured path). All requests go through it.

Inside DispatcherServlet:

  1. Request received
  2. HandlerMapping finds the controller method matching the URL
  3. HandlerInterceptors run (Spring's parallel to filters, but Spring-specific)
  4. Argument resolvers convert request data to method parameters
  5. Controller method executes
  6. Return value is processed by HandlerExceptionResolver or MessageConverter
  7. Response written

Spring Security adds Filters in the servlet chain that run before DispatcherServlet.

Modern Servlet (Jakarta EE)

The Servlet API moved from javax.servlet (Java EE) to jakarta.servlet (Jakarta EE) in 2018. Modern code uses the jakarta.servlet package.

This affects:

For older codebases, the migration is mostly mechanical (package renames). Some libraries provide both versions.

The reactive alternative

Spring WebFlux and reactive frameworks bypass the Servlet API. They use Netty (or Reactor Netty) directly. The model is event-loop-based rather than thread-per-request.

When does reactive matter:

When servlet model is fine:

With virtual threads (Java 21+), the servlet model's traditional weakness — blocking I/O tying up threads — is largely solved. The case for reactive narrows.

Common failure patterns

Further Reading