We are still cooking the magic in the way!
Cookies
Cookies
A cookie is a small piece of data that the server sends to the browser, and the browser automatically attaches to every subsequent request to the same origin. Despite their simplicity, cookies are the bedrock of web identity: they carry session tokens, remember user preferences, and power analytics. This lesson covers how to create and read cookies in a Jakarta Servlet, what every attribute controls, and the security decisions a working developer must make deliberately.
How Cookies Flow
The mechanics follow HTTP precisely. When a server wants to set a cookie it adds a Set-Cookie response header. The browser stores the cookie and includes it in a Cookie request header on every matching future request. The Servlet API wraps this header dance in the Cookie class so you work with objects rather than raw header strings.
maxAge to 0.
Creating a Cookie
Construct a jakarta.servlet.http.Cookie, configure its attributes, and add it to the response before getWriter() or getOutputStream() is committed:
Reading Cookies
request.getCookies() returns all cookies the browser sent, or null if none were included. There is no "get by name" method — you iterate the array yourself. A utility method is worth writing once and reusing across servlets:
At the call site:
Cookie Attributes in Detail
Each attribute on Cookie maps directly to a Set-Cookie directive. Getting them wrong costs you either functionality or security.
Max-Age and Expires — Persistence vs. Session
If you never call setMaxAge(), the cookie is a session cookie: it exists only in memory and disappears when the browser closes. Calling setMaxAge(seconds) with a positive integer makes it a persistent cookie — the browser writes it to disk and sends it again even after a restart.
setMaxAge(0)— instructs the browser to delete the cookie immediately (the delete pattern).setMaxAge(-1)— equivalent to never calling it; the cookie is session-scoped.setMaxAge(n)where n > 0 — persist for n seconds from the moment the response is received.
Expires attribute is an absolute date that is fragile if the client clock is wrong. Max-Age is relative and therefore reliable. Modern browsers honour both, but Max-Age takes precedence when both are present.
Domain and Path — Scoping Which Requests Include the Cookie
setDomain() controls which hostnames receive the cookie. Leaving it unset is the safe default: the cookie is sent only to the exact host that set it. Setting domain=example.com (with a leading dot implied) causes the cookie to be sent to api.example.com, app.example.com, etc. — useful for SSO across subdomains, but it widens the attack surface.
setPath() restricts cookie sending to URLs under that path. Setting /admin means only requests to /admin/* carry the cookie. Setting / means all requests to the host carry it — appropriate for a user identity cookie, but wasteful (and slightly risky) for a cookie that is only needed by one section of your site.
HttpOnly — Blocking JavaScript Access
An HttpOnly cookie is invisible to document.cookie in JavaScript. This single flag eliminates the most common consequence of XSS attacks: a script injected into your page cannot steal the session cookie. Always set this flag for authentication cookies.
Secure — HTTPS Enforcement
A Secure cookie is transmitted only over TLS. Without this flag, the cookie rides along with HTTP requests in plaintext — readable by anyone on the same network segment. In production, all sensitive cookies must be Secure. During local development you may omit it (since localhost is typically HTTP), but it must be on before you deploy.
SameSite — Cross-Site Request Forgery Mitigation
The Servlet API did not add native SameSite support until very recently. In Jakarta EE 10 / Servlet 6.0, Cookie gained setAttribute("SameSite", "Strict"). On older containers you must set it by writing the raw header:
- Strict — cookie is never sent with cross-site requests at all. Strongest CSRF protection; breaks flows where a link from another site should land the user in their existing session.
- Lax — cookie is sent with top-level navigations (clicking a link) but not with embedded sub-requests (image, iframe, fetch). The browser default in most modern browsers.
- None — cookie is always sent cross-site. Requires
Secure. Use only for third-party integrations (payment widgets, embedded maps).
Deleting a Cookie
There is no "delete" API. Send a replacement cookie with the same name and path, but with maxAge set to 0:
theme set on path / is a different cookie from one named theme set on path /app. Sending a zero-age cookie on the wrong path leaves the original untouched and creates a new expired one — which the browser ignores. This is a common, hard-to-debug bug.
Practical Security Checklist
Before shipping any cookie to production, verify:
HttpOnlyis set on all authentication and session cookies.Secureis set — and your load balancer or reverse proxy enforces HTTPS.SameSite=LaxorStrictis set on session cookies.- Cookie values that carry security-sensitive data (session IDs, CSRF tokens) are randomly generated and not user-controlled — never store the username itself as a cookie value.
Pathis as narrow as functionally possible.- Persistent cookies expire in a reasonable time window — not 10 years.
Summary
Creating a cookie takes three lines: construct a Cookie, configure its attributes, call resp.addCookie(). Reading one requires iterating req.getCookies() — a utility method saves repetition. The real craft is in the attributes: maxAge decides persistence, path and domain scope delivery, and httpOnly + secure + sameSite collectively determine whether your cookies are safe to carry sensitive identity data. In the next lesson you will see how sessions use a single HttpOnly cookie as their transport mechanism — and why that design decision matters.