Sessions, Cookies & Filters

HttpSession Basics

18 min Lesson 2 of 13

HttpSession Basics

HTTP is a stateless protocol — every request arrives as if the server has never seen that client before. The servlet container solves this with HttpSession: a server-side map of attributes keyed to a unique session ID that is transported to the browser as a cookie (or via URL rewriting when cookies are disabled). This lesson covers the three core operations every servlet developer must know cold: obtaining a session, writing attributes into it, and reading them back out, along with the scope rules that govern when those attributes are visible.

Obtaining a Session

The entry point is a single method on HttpServletRequest:

HttpSession session = request.getSession(); // create if absent HttpSession session = request.getSession(true); // same — create if absent HttpSession session = request.getSession(false); // return null if no session exists

The boolean argument controls the creation policy. Calling getSession() or getSession(true) is appropriate for login flows and any action that first establishes user state. Calling getSession(false) is appropriate when you want to check whether a session already exists without accidentally creating an empty one — for example in a security filter that guards a protected area.

Under the hood: When the container creates a new session it generates a cryptographically random session ID (e.g. JSESSIONID=A3F9...), stores it in a Set-Cookie response header, and keeps the attribute map in memory (or a distributed store). On every subsequent request the browser returns that cookie and the container looks up the map.

Storing Session Attributes

HttpSession.setAttribute(String name, Object value) places any serialisable object into the session map under a string key. A realistic login servlet might look like this:

import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import java.io.IOException; @WebServlet("/login") public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username"); String password = req.getParameter("password"); // authenticate — replace with real DB lookup if ("admin".equals(username) && "secret".equals(password)) { // Invalidate any pre-existing session to prevent session fixation HttpSession old = req.getSession(false); if (old != null) old.invalidate(); // Create a brand-new session HttpSession session = req.getSession(true); session.setAttribute("authenticatedUser", username); session.setAttribute("loginTime", System.currentTimeMillis()); resp.sendRedirect(req.getContextPath() + "/dashboard"); } else { resp.sendRedirect(req.getContextPath() + "/login?error=1"); } } }
Session fixation attack: Always call getSession(false) + invalidate() on the old session before creating a new one at login. If you reuse the pre-login session, an attacker who planted a known session ID in the victim's browser gains access after the victim authenticates.

Reading Session Attributes

HttpSession.getAttribute(String name) returns Object (or null if the key is absent). You cast to the expected type:

@WebServlet("/dashboard") public class DashboardServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session = req.getSession(false); if (session == null || session.getAttribute("authenticatedUser") == null) { // No valid session — redirect to login resp.sendRedirect(req.getContextPath() + "/login"); return; } String user = (String) session.getAttribute("authenticatedUser"); Long loginTime = (Long) session.getAttribute("loginTime"); req.setAttribute("user", user); req.setAttribute("loginTime", loginTime); req.getRequestDispatcher("/WEB-INF/views/dashboard.jsp").forward(req, resp); } }

A few important details in this snippet worth noting:

  • getSession(false) is used deliberately — a GET to the dashboard should never create a session.
  • The null check on both the session and the attribute guards against expired or manually invalidated sessions.
  • The cast is unavoidable because the session map stores Object. Using a typed wrapper class (see tip below) removes scattered casts from your codebase.

Removing and Invalidating

Two operations complete the attribute lifecycle:

session.removeAttribute("loginTime"); // remove one key, session stays alive session.invalidate(); // destroy the entire session — use at logout

After invalidate() any further call on that HttpSession reference throws IllegalStateException. Always redirect immediately after invalidating so the response does not touch the session again.

Session Scope vs Request Scope

Understanding scope prevents subtle bugs. There are four scopes in a Jakarta EE web application:

  • Request scope (request.setAttribute) — lives for exactly one request/response cycle. Use it to pass data from a servlet to a JSP during a forward.
  • Session scope (session.setAttribute) — lives across multiple requests from the same client until the session expires or is invalidated. Use it for authenticated user identity, shopping cart, locale preference.
  • Application scope (getServletContext().setAttribute) — shared across all sessions and all clients for the lifetime of the web application. Use it for read-only configuration or global counters.
  • Page scope (JSP only) — limited to a single JSP page execution.
Type-safe session wrapper pattern: Rather than scattering raw getAttribute casts throughout your servlets, encapsulate session access in a dedicated class:
public final class UserSession { private static final String KEY = "userSession"; private String username; private long loginTime; private UserSession() {} public static UserSession create(HttpSession session, String username) { UserSession us = new UserSession(); us.username = username; us.loginTime = System.currentTimeMillis(); session.setAttribute(KEY, us); return us; } public static UserSession get(HttpSession session) { return (UserSession) session.getAttribute(KEY); // one cast, one place } public String getUsername() { return username; } public long getLoginTime() { return loginTime; } }
Now every servlet calls UserSession.get(session) — no casts, no magic strings, and refactoring the key name is a one-line change.

Inspecting a Session

HttpSession exposes several diagnostic methods that are useful in filters, admin pages, and logging:

session.getId(); // the session ID string (e.g. "A3F9C2...") session.getCreationTime(); // epoch ms when the session was first created session.getLastAccessedTime(); // epoch ms of the most recent request that used this session session.getMaxInactiveInterval(); // idle timeout in seconds (-1 = never expire) session.isNew(); // true if the container just created it this request

Summary

The session API is small but the discipline around it matters enormously. Use getSession(false) whenever you are reading — never create a session as a side effect of checking for one. Store only what genuinely needs to survive across requests: authenticated identity, preferences, cart contents. Keep attribute values small and serialisable. And always invalidate then redirect at logout. In the next lesson you will see how the container manages session lifetime through timeout configuration and lifecycle events.