Skip to content
Home
Java Servlets Guide: Web Apps, Filters, Sessions, and Containers

Java Servlets Guide: Web Apps, Filters, Sessions, and Containers

Java Java 7 min read 1441 words Beginner ExcellentWiki Editorial Team

Java Servlets have been the foundation of server-side Java web development since the late 1990s. Despite the rise of frameworks like Spring MVC and JSF, every Java web framework runs on top of the Servlet API. Understanding servlets — request/response handling, session management, filters, and lifecycle — gives you the low-level knowledge to debug, optimize, and build custom web infrastructure. This guide covers Jakarta Servlet 6, the latest specification under Jakarta EE 10/11.

Servlet Architecture and Lifecycle

A servlet is a Java class that handles HTTP requests. The servlet container (Tomcat, Jetty, Undertow) manages the lifecycle:

  1. Loading: Container loads the servlet class
  2. Instantiation: new() called once
  3. Initialization: init(ServletConfig) called once
  4. Request handling: service(request, response) called per request
  5. Destruction: destroy() called before unloading

The Jakarta Servlet Specification (§2.3) defines this contract. The GenericServlet base class implements Servlet and ServletConfig. HttpServlet extends it with HTTP-specific methods (doGet, doPost, etc.).

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {

    private String greeting;

    @Override
    public void init() {
        greeting = getServletConfig().getInitParameter("greeting");
        if (greeting == null) greeting = "Hello";
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setContentType("text/html");
        PrintWriter out = resp.getWriter();
        out.println("<h1>" + greeting + ", World!</h1>");
    }
---

The @WebServlet annotation (Servlet 3.0+) replaces web.xml configuration for most use cases. The container discovers annotated classes during deployment.

Request and Response

HttpServletRequest provides access to all HTTP request data — parameters, headers, cookies, body, and attributes. HttpServletResponse controls the response status, headers, and body.

@WebServlet("/search")
public class SearchServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        String query = req.getParameter("q");
        if (query == null || query.isBlank()) {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing query");
            return;
        }

        // Forward processing
        req.setAttribute("results", searchService.search(query));
        req.getRequestDispatcher("/results.jsp").forward(req, resp);
    }
---

Key methods on HttpServletRequest:

  • getParameter(name) — single query/body parameter
  • getParameterValues(name) — multi-valued parameters (checkboxes)
  • getHeader(name) — request header
  • getSession(true) — get or create HttpSession
  • getRequestDispatcher(path) — forward/include to another resource
  • getPart(name) — multipart file upload (Servlet 3.0+)

The Servlet API documentation covers the full method reference.

Session Management

HTTP is stateless. Servlets maintain state via HttpSession, which maps server-side attributes to a session ID (typically stored as a cookie or URL rewrite).

@WebServlet("/login")
public class LoginServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        String username = req.getParameter("username");
        User user = authService.authenticate(username, req.getParameter("password"));

        if (user != null) {
            HttpSession session = req.getSession();
            session.setAttribute("user", user);
            session.setMaxInactiveInterval(30 * 60);  // 30 minutes
            resp.sendRedirect("/dashboard");
        } else {
            resp.sendRedirect("/login?error=1");
        }
    }
---

Session storage options:

  • In-memory (default) — lost on restart, not clusterable
  • Database-backed — persists across restarts
  • Redis-backed — cluster-ready, fast (via Spring Session)
  • Token-based (JWT) — stateless, no server-side session (REST APIs)

Container clustering requires session replication or a distributed cache. Tomcat’s SimpleTcpCluster or external session stores address this.

Filters

Filters intercept requests before they reach servlets and after servlets produce responses. They are ideal for cross-cutting concerns — authentication, logging, compression, CORS, and input validation.

@WebFilter("/*")
public class LoggingFilter implements Filter {

    private static final Logger log = LoggerFactory.getLogger(LoggingFilter.class);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) request;
        long start = System.currentTimeMillis();

        try {
            chain.doFilter(request, response);
        } finally {
            long elapsed = System.currentTimeMillis() - start;
            log.info("{} {} {} ({}ms)", req.getMethod(), req.getRequestURI(),
                ((HttpServletResponse) response).getStatus(), elapsed);
        }
    }
---

The @WebFilter annotation applies the filter to URL patterns. Filters execute in the order declared in web.xml (or by @Order in Servlet 4.0+). The servlet specification (§6.1) defines the filter chain contract.

Listeners

Listeners react to lifecycle events — context initialization, session creation, and attribute changes.

@WebListener
public class AppContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        sce.getServletContext().setAttribute("startTime", Instant.now());
        // Initialize shared resources (connection pools, caches)
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // Cleanup
    }
---

Use listeners for startup/shutdown logic, replacing static initializer blocks.

Security

Servlet security can be declarative (web.xml or annotations) or programmatic:

  • @ServletSecurity annotation with @HttpConstraint and @HttpMethodConstraint
  • login-config in web.xml (BASIC, FORM, DIGEST, CLIENT-CERT)
  • Programmatic: request.isUserInRole("admin"), request.authenticate(response)

For Spring Boot applications, Spring Security wraps the servlet filter chain, providing OAuth2, SAML, and JWT support on top of the same servlet foundation.

Servlet Containers Compared

ContainerStartupFootprintBest For
Apache TomcatFastSmall (10MB)Production web apps
Eclipse JettyFastestSmall (2MB)Embedded, microservices
UndertowFastSmallEmbedded, high concurrency
WildFlySlowLarge (50MB+)Full Jakarta EE stack

Embedded containers (Spring Boot uses Tomcat by default) allow running servlets without a standalone server. For cloud deployments, embedded Tomcat or Undertow is standard.

WebSocket Support

Jakarta Servlet 6+ integrates WebSocket support for real-time bidirectional communication:

@ServerEndpoint("/chat/{room}")
public class ChatEndpoint {

    @OnOpen
    public void onOpen(Session session, @PathParam("room") String room) {
        session.getUserProperties().put("room", room);
        broadcast(room, "User joined");
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        String room = (String) session.getUserProperties().get("room");
        broadcast(room, message);
    }

    @OnClose
    public void onClose(Session session) {
        String room = (String) session.getUserProperties().get("room");
        broadcast(room, "User left");
    }
---

WebSocket endpoints coexist with servlets in the same container. Use them for dashboards, notifications, collaborative editing, and live data feeds.

Error Handling and Status Codes

Standard HTTP status codes for servlet error responses:

  • 400 Bad Request — malformed input, missing parameters
  • 401 Unauthorized — missing or invalid authentication
  • 403 Forbidden — authenticated but not authorized
  • 404 Not Found — resource does not exist
  • 405 Method Not Allowed — wrong HTTP method
  • 409 Conflict — resource state conflict
  • 500 Internal Server Error — unexpected server failure
  • 503 Service Unavailable — temporary overload or maintenance

Map exceptions consistently using error response objects rather than HTML error pages for REST APIs.

Thread Safety in Servlets

The Servlet specification (§2.3.3) states that servlets are not thread-safe by default. A single HttpServlet instance handles all requests concurrently. Shared fields must be protected:

@WebServlet("/counter")
public class CounterServlet extends HttpServlet {

    private final AtomicLong requestCount = new AtomicLong(0);

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        long count = requestCount.incrementAndGet();
        resp.setContentType("text/plain");
        resp.getWriter().println("Request #" + count);
    }
---

Instance variables in servlets should be either thread-safe (atomic classes, concurrent collections) or immutable. Local variables in service(), doGet(), and other methods are thread-local and safe.

Asynchronous Servlets

Servlet 3.0 introduced asynchronous processing, allowing the container thread to return to the pool while the response waits for an external event:

@WebServlet(urlPatterns = "/async", asyncSupported = true)
public class AsyncServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        AsyncContext ctx = req.startAsync();
        ctx.setTimeout(10000);

        ctx.start(() -> {
            try {
                String result = expensiveComputation();
                ctx.getResponse().getWriter().write(result);
            } catch (Exception e) {
                ctx.complete();
            }
        });
    }
---

Async servlets are used for long-polling, SSE (Server-Sent Events), and integrations with slow external services. With virtual threads (Java 21+), async servlets are less necessary — simply run blocking code in a virtual thread.

FAQ

Q: What is the difference between forward() and sendRedirect()? A: forward() (server-side) executes the destination on the same request — the URL does not change. sendRedirect() (client-side) sends a 302 response — the browser issues a new request to the target URL.

Q: How do I handle file uploads in a servlet? A: Annotate the servlet with @MultipartConfig. Use request.getPart("file") and Part.write(filename). The file is stored as a temporary file; move it to permanent storage.

Q: What servlet container should I use for development? A: Tomcat is the most common choice. It is well-documented, compatible with all major IDEs, and the reference implementation for the Servlet specification.

Q: Is the Servlet API still relevant with Spring MVC? A: Yes. Spring MVC’s DispatcherServlet is itself a HttpServlet. Understanding servlets helps debug Spring Boot applications at the container level.

Q: How do I configure HTTPS in Tomcat? A: Add a <Connector> in server.xml with SSLEnabled="true", keystoreFile, and keystorePass. Or terminate SSL at a reverse proxy (Nginx, HAProxy) and proxy HTTP to Tomcat.

Servlet container tuning for production includes setting maxThreads based on expected concurrency (typically 200-400 per CPU core for traditional threads, or much higher with virtual threads), enabling static resource compression, and configuring connectionTimeout to drop idle clients. Tomcat’s Executor element provides a reusable thread pool shared across connectors. These optimizations are documented in the Tomcat configuration reference and apply to most production deployments.

Servlet 6.0 (Jakarta EE 11) introduces support for virtual threads in the servlet container. Tomcat 11+ and Jetty 12+ can execute servlet requests on virtual threads, allowing thousands of concurrent connections with the traditional blocking I/O programming model. Enable it with -Dreactor.netty.ioWorkerCount=0 in Undertow or by configuring the virtual thread executor as Tomcat’s connector executor.

For authoritative reference, read the Jakarta Servlet Specification and Head First Servlets and JSP. See also our Java Enterprise Guide for the broader Jakarta EE context.

For a comprehensive overview, read our article on Java 17 21 Features.

Section: Java 1441 words 7 min read Beginner 756 articles in section Report inaccuracy Back to top