Sessions, Cookies & Filters

State on a Stateless Protocol

18 min Lesson 1 of 13

State on a Stateless Protocol

Every modern web application relies on the idea that the server remembers something about a user across multiple requests — whether that user is logged in, what is in their shopping cart, or which language they prefer. Yet HTTP, the protocol that carries every one of those requests, was deliberately designed to be stateless. Understanding the tension between that design choice and the real-world need for continuity is the foundation of this entire tutorial.

Why HTTP Is Stateless

When a browser sends a GET request to /dashboard, the HTTP/1.1 specification guarantees nothing about what the server already knows about that client. Each request arrives as a self-contained message with headers and an optional body — but with no built-in identity. Once the server sends a response, it is free to forget the exchange ever happened.

This was an intentional design choice, not an oversight. Roy Fielding's REST constraints (and the earlier HTTP/1.0 spec) chose statelessness because it delivers three concrete engineering benefits:

  • Scalability: Any server in a cluster can handle any request because no client-specific memory is held in the server process. A load balancer can route freely.
  • Reliability: If the server crashes, a retry from the client to a different instance works correctly — there is no session to rebuild.
  • Visibility: An intermediary (proxy, CDN, monitoring tool) can understand a request fully from the message alone, without needing prior context.
Statelessness is a protocol property, not a server property. Your server process can absolutely hold state in memory — it just has no HTTP-level mechanism to tie that state to a specific client across requests. The strategies below are how we close that gap.

The Problem: Recognising Returning Visitors

Consider a simple login flow. The user POSTs credentials to /login, the server validates them, and now must communicate "this client is authenticated" on every subsequent request. But the next request — say GET /orders — arrives with no inherent link to the login. How does the server associate the two?

There are four classical strategies. Each has distinct trade-offs around security, scalability, and implementation complexity.

Strategy 1: HTTP Sessions (Server-Side State)

The server generates an opaque, hard-to-guess token — the session ID — and stores a map of session IDs to user data in its own memory (or a shared store like Redis). The token is sent to the browser, which echoes it back on every subsequent request.

// Server generates a session on login HttpSession session = request.getSession(true); // creates if absent session.setAttribute("userId", user.getId()); session.setAttribute("role", user.getRole()); // Every subsequent request can read it Long userId = (Long) request.getSession(false) // null if no session .getAttribute("userId");

The browser never sees the actual user ID — only the random session token (typically delivered via a JSESSIONID cookie). This keeps sensitive data server-side, which is a significant security advantage. The downside is that the server must maintain this state and, in a clustered deployment, share it across nodes.

Strategy 2: Cookies (Client-Side State)

Instead of keeping data on the server, encode it directly into a cookie that the browser stores and re-sends. A Set-Cookie response header plants the cookie; every subsequent request to the same domain automatically includes a Cookie header containing it.

// Writing a cookie in a Servlet Cookie langPref = new Cookie("lang", "ar"); langPref.setMaxAge(60 * 60 * 24 * 30); // 30 days, in seconds langPref.setPath("/"); langPref.setHttpOnly(true); // not readable by JavaScript langPref.setSecure(true); // HTTPS only response.addCookie(langPref); // Reading it back on the next request for (Cookie c : request.getCookies()) { if ("lang".equals(c.getName())) { String lang = c.getValue(); } }

Cookies are ideal for non-sensitive preferences (locale, theme) but should never carry confidential data in plain text. Cookie storage is bounded (~4 KB per cookie) and the client controls whether to accept them.

Strategy 3: URL Rewriting

When cookies are disabled, the session token can be embedded directly in URLs as a query parameter: /orders?jsessionid=abc123. The Servlet API handles this transparently through response.encodeURL():

// Always encode outbound URLs so the container can append the session // token automatically if cookies are unavailable String safeUrl = response.encodeURL("/orders"); // Renders as /orders?jsessionid=abc123 when cookies are off // Renders as /orders when cookies are on (no need to embed) out.println("<a href=\"" + safeUrl + "\">My Orders</a>");
URL rewriting leaks the session token. It appears in browser history, server logs, and the Referer header. Use it only as a fallback when cookies are truly unavailable, and never on HTTPS-enforced paths that handle sensitive data.

Strategy 4: Tokens (Stateless Authentication)

Modern APIs often avoid server-side sessions entirely. Instead, after login the server issues a signed token — most commonly a JSON Web Token (JWT) — that the client stores (in localStorage or a secure cookie) and sends in every request, usually as a Bearer header.

// Conceptual HTTP exchange: // 1. Client sends credentials POST /api/login {"username":"ali","password":"secret"} // 2. Server responds with a signed token HTTP/1.1 200 OK {"token":"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGkiLCJyb2xlIjoiVVNFUiIsImV4cCI6MTcxOTAwMDAwMH0.SIGNATURE"} // 3. Client sends the token with every protected call GET /api/orders Authorization: Bearer eyJhbGciOiJIUzI1NiJ9... // 4. Server verifies the signature — no DB lookup needed

Because the token is self-contained and cryptographically verified, the server holds no session state at all. This scales perfectly horizontally. The trade-off: tokens cannot be revoked before they expire without introducing a server-side deny-list, which re-introduces state.

Comparing the Strategies

  • HTTP Sessions: Easiest to implement in Jakarta EE, revocable instantly, but require shared state storage in clustered environments.
  • Cookies: Great for low-sensitivity preferences, handled entirely by the browser, but limited in size and can be stolen if not secured.
  • URL Rewriting: Cookie-independent fallback, but exposes tokens in logs and history — use reluctantly and briefly.
  • JWT / Tokens: Naturally stateless and cluster-friendly, but revocation and token size are practical concerns. Dominant in REST API and microservices contexts.
Choose based on your deployment model. A traditional server-rendered Jakarta EE application almost always uses HttpSession backed by a distributed store (Memcached, Redis, Infinispan). A pure REST API serving a JavaScript SPA leans toward JWT. Many real systems use both: a session for the web UI, tokens for the API layer.

What the Jakarta Servlet API Provides

The Servlet specification (Jakarta Servlet 6.x) handles sessions and cookies at the container level. The container — Tomcat, WildFly, GlassFish — manages cookie headers, session expiry, and URL rewriting transparently. Your code calls clean APIs:

  • request.getSession() — get or create an HttpSession
  • session.setAttribute(String, Object) — store any serialisable object
  • response.addCookie(Cookie) — instruct the browser to store a cookie
  • response.encodeURL(String) — append session ID to a URL when needed

The next lesson dives deep into HttpSession. But the key insight to carry forward is this: every "stateful" behaviour you see in a web application is built on top of a fundamentally stateless protocol, using one of these four mechanisms. Knowing when to use each one — and understanding the security implications of each — separates professional web developers from those who just copy-paste session code without thinking.

Summary

HTTP is stateless by design, giving you scalability and simplicity at the cost of built-in identity tracking. The four strategies for bridging this gap are server-side sessions, cookies, URL rewriting, and signed tokens (JWT). Jakarta EE's Servlet API makes sessions and cookies straightforward to implement; the rest of this tutorial explores each mechanism in depth, starting with HttpSession.