State on a Stateless Protocol
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.
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.
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.
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():
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.
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.
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 anHttpSessionsession.setAttribute(String, Object)— store any serialisable objectresponse.addCookie(Cookie)— instruct the browser to store a cookieresponse.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.