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.
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.
When an HTTP request arrives:
doGet, doPost, etc.HttpServletResponseEach request runs on a thread from the container's thread pool. With virtual threads (Java 21+), this can be a virtual thread.
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 react to lifecycle events:
ServletContextListener: application start/stopHttpSessionListener: session create/destroyServletRequestListener: request start/endUsed for one-time initialization, cleanup, observability. Spring Boot's ApplicationContext lifecycle hooks ultimately bottom out in servlet listeners.
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.
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.
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:
HandlerMapping finds the controller method matching the URLHandlerInterceptors run (Spring's parallel to filters, but Spring-specific)HandlerExceptionResolver or MessageConverterSpring Security adds Filters in the servlet chain that run before DispatcherServlet.
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:
javax)For older codebases, the migration is mostly mechanical (package renames). Some libraries provide both versions.
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.