Sessions, Cookies & Filters

URL Rewriting & Session Tracking

18 min Lesson 5 of 13

URL Rewriting & Session Tracking

HTTP is a stateless protocol: each request stands alone and the server retains nothing between calls. Sessions give the illusion of continuity, but they require the browser and server to exchange a token on every request so the server can look up the right HttpSession object. The standard token is a cookie named JSESSIONID. But cookies can be disabled — by the browser, by the user, or by a corporate proxy. When that happens the server falls back to URL rewriting, embedding the session token directly in every link and form action as a query parameter or path segment.

How the Container Tracks Sessions

The Jakarta Servlet specification defines three session-tracking modes that can be combined:

  • COOKIE — the container sets a Set-Cookie: JSESSIONID=<token>; HttpOnly; Path=/ header on the first response and the browser returns it automatically on every subsequent request.
  • URL — the token is appended to URLs as ;jsessionid=<token>, for example /app/dashboard;jsessionid=ABC123. This works even when cookies are blocked.
  • SSL — the session is tied to the TLS session ID (rarely used in practice).

The container negotiates automatically: it sends the cookie on the first response and checks on the next request whether the cookie came back. If the cookie is absent (either never sent or deleted), it falls through to URL rewriting.

Servlet API entry point for URL rewriting: HttpServletResponse.encodeURL(String url) and HttpServletResponse.encodeRedirectURL(String url). If the container determines the client accepts cookies, these methods return the URL unchanged. If cookies are not yet confirmed or are absent, they append ;jsessionid=.... Always call them — at worst they are a no-op.

encodeURL in Practice

Every hyperlink and form action in a server-rendered page should be wrapped with encodeURL:

import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import java.io.IOException; import java.io.PrintWriter; @jakarta.servlet.annotation.WebServlet("/dashboard") public class DashboardServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { HttpSession session = req.getSession(false); // do NOT create if absent if (session == null || session.getAttribute("user") == null) { resp.sendRedirect(resp.encodeRedirectURL(req.getContextPath() + "/login")); return; } resp.setContentType("text/html;charset=UTF-8"); PrintWriter out = resp.getWriter(); // Every internal link must go through encodeURL String profileUrl = resp.encodeURL(req.getContextPath() + "/profile"); String logoutUrl = resp.encodeURL(req.getContextPath() + "/logout"); String settingsUrl = resp.encodeURL(req.getContextPath() + "/settings"); out.println("<!DOCTYPE html><html><body>"); out.println("<nav>"); out.println(" <a href='" + profileUrl + "'>My Profile</a>"); out.println(" <a href='" + settingsUrl + "'>Settings</a>"); out.println(" <a href='" + logoutUrl + "'>Logout</a>"); out.println("</nav>"); out.println("</body></html>"); } }

When cookies are active the output is simply /app/profile. When cookies are disabled the output becomes /app/profile;jsessionid=A7F3D2C1B8E94560. The HTML is otherwise identical — your code stays portable.

Redirects Need encodeRedirectURL

encodeURL is for links in HTML. encodeRedirectURL is for the Location header sent with a redirect. Use the right method for the right context:

// After a form POST — redirect to prevent re-submission (Post/Redirect/Get pattern) String nextPage = req.getContextPath() + "/order-confirmation"; resp.sendRedirect(resp.encodeRedirectURL(nextPage));
Always use req.getContextPath() when building URLs. Hard-coding /app/... breaks the moment the application is deployed at a different context root. getContextPath() returns the correct prefix — empty string for a root deployment, /myapp otherwise.

Session Tracking Mode Configuration

You can restrict or control tracking modes in web.xml to enforce a security policy:

<!-- web.xml -- require cookie-only tracking (most secure) --> <session-config> <tracking-mode>COOKIE</tracking-mode> <cookie-config> <http-only>true</http-only> <secure>true</secure> <!-- HTTPS only --> <max-age>-1</max-age> <!-- session cookie (dies with browser) --> </cookie-config> </session-config>

Or programmatically during application startup:

import jakarta.servlet.SessionTrackingMode; import jakarta.servlet.ServletContextListener; import jakarta.servlet.annotation.WebListener; import java.util.EnumSet; @WebListener public class AppContextListener implements ServletContextListener { @Override public void contextInitialized(jakarta.servlet.ServletContextEvent sce) { // Restrict to cookie-only; URL rewriting is disabled sce.getServletContext().setSessionTrackingModes( EnumSet.of(SessionTrackingMode.COOKIE) ); } }
Disabling URL rewriting is a security improvement. When session IDs appear in URLs they can leak through: the browser history, the Referer header sent to third-party sites, server access logs, and shared bookmarks. For most production applications you should enforce COOKIE-only tracking and require HTTPS, making the token invisible outside the encrypted channel.

JSP and JSTL: c:url Handles This For You

When you write views in JSP, the JSTL <c:url> tag automatically calls encodeURL internally so you never have to remember:

<%@ taglib prefix="c" uri="jakarta.tags.core" %> <!-- c:url encodes the session ID when needed, resolves context path --> <a href="<c:url value='/profile'/>">My Profile</a> <a href="<c:url value='/logout'/>">Logout</a> <!-- Also works with query parameters --> <a href="<c:url value='/orders'> <c:param name='page' value='2'/> </c:url>">Next page</a>

This is one of the reasons JSP/JSTL views are still preferred over manually constructing HTML in servlet code — session management boilerplate is handled transparently.

How the Container Parses the Incoming Session ID

On every incoming request request.getSession() (and its variants) trigger the following internal sequence:

  1. Check the Cookie header for JSESSIONID.
  2. If absent, check the request URI for ;jsessionid=<token>.
  3. Look up the token in the session store (memory, database, Redis, etc.).
  4. Return the HttpSession if found and not expired; otherwise create a new one (if getSession(true)) or return null (if getSession(false)).

You can inspect which mechanism delivered the current session:

HttpSession session = req.getSession(false); if (session != null) { boolean viaCookie = req.isRequestedSessionIdFromCookie(); boolean viaUrl = req.isRequestedSessionIdFromURL(); boolean valid = req.isRequestedSessionIdValid(); System.out.printf("Session %s — cookie=%b url=%b valid=%b%n", session.getId(), viaCookie, viaUrl, valid); }

Summary

URL rewriting is the servlet container's fallback mechanism for session continuity when cookies are unavailable. The two key methods are encodeURL for hyperlinks and encodeRedirectURL for redirect responses — always call them so your application works in both cookie and cookie-less environments. For production, enforce COOKIE-only tracking with HttpOnly and Secure flags to keep session tokens out of logs and browser history. When writing JSP views, let <c:url> handle the encoding for you.