Why Closing the Browser Doesn’t End Your Java Session – HttpSession and Cookie Sharing Across Ports

This article explores the inner workings of Java HttpSession in Tomcat, explains how sessions are stored in a thread‑safe map, examines the relationship between sessions and cookies, and investigates why browsers share cookies across different ports on the same host, leading to unexpected session behavior.

Programmer DD
Programmer DD
Programmer DD
Why Closing the Browser Doesn’t End Your Java Session – HttpSession and Cookie Sharing Across Ports

Introduction

Session is a term that is easily misunderstood. It is often associated with web‑system sessions that control login state, but closing the browser does not terminate a Tomcat HttpSession. In Tomcat, a session is an implementation of the HttpSession interface.

Session Essence

The Tomcat source places session‑related classes under org.apache.catalina.session. The concrete implementation used at runtime is StandardSession, accessed through the façade StandardSessionFacade, which hides many methods while improving security.

Session attributes are stored in a thread‑safe ConcurrentHashMap:

/**
 * The collection of user data attributes associated with this Session.
 */
protected ConcurrentMap<String, Object> attributes = new ConcurrentHashMap<>();

Session creation and lookup are handled by a manager class. The manager keeps an active‑session map keyed by session identifier:

/**
 * The set of currently active Sessions for this Manager, keyed by
 * session identifier.
 */
protected Map<String, Session> sessions = new ConcurrentHashMap<>();

Finding a session by id is straightforward:

@Override
public Session findSession(String id) throws IOException {
    if (id == null) {
        return null;
    }
    return sessions.get(id);
}

Creating a new session involves several steps, including checking the maximum active sessions, generating an id if necessary, and initializing timing information:

@Override
public Session createSession(String sessionId) {
    if ((maxActiveSessions >= 0) && (getActiveSessions() >= maxActiveSessions)) {
        rejectedSessions++;
        throw new TooManyActiveSessionsException(sm.getString("managerBase.createSession.ise"), maxActiveSessions);
    }
    Session session = createEmptySession();
    session.setNew(true);
    session.setValid(true);
    session.setCreationTime(System.currentTimeMillis());
    session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);
    String id = sessionId;
    if (id == null) {
        id = generateSessionId();
    }
    session.setId(id);
    sessionCounter++;
    SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
    synchronized (sessionCreationTiming) {
        sessionCreationTiming.add(timing);
        sessionCreationTiming.poll();
    }
    return session;
}

The manager creates a session when a request contains no matching JSESSIONID cookie; otherwise it retrieves the existing session.

Cookie Overview

Cookies accompany HTTP requests and responses. A typical first request to http://localhost:8080/test1 contains no JSESSIONID cookie, so the server generates one and sends it back in a Set‑Cookie header. Subsequent requests include the cookie, allowing the server to locate the corresponding session.

Two important cookie attributes are:

Domain : the host for which the cookie is valid. By default it is the request host; setting a broader domain (e.g., .example.com) allows subdomains to share the cookie.

Path : the URL path prefix for which the cookie is sent. A cookie with /test/ is sent to any resource under that path, while a cookie with /test/cd/ is not sent to /test/dd/.

Issue Investigation

Scenario 1 – Redirect between ports

A request to localhost:8081/test creates a session (id = CAAB6AED34716A0394705BDE8CAC0042) and redirects to http://localhost:8080/test1. The redirected request includes the same cookie, even though the target port differs. Tomcat on port 8080 does not recognize the id, creates a new session, and sends a new Set‑Cookie. Subsequent accesses to 8081 receive the new id generated by the 8080 server, causing continuous session recreation.

Scenario 2 – No redirect, separate services

Two independent services run on ports 8080 and 8081. Accessing 8081/test creates a session and sets a cookie. When 8080/test1 is accessed, the browser sends the cookie from 8081 because the domain (localhost) is identical, even though the ports differ. The 8080 service cannot find the session, creates a new one, and the cycle repeats.

Inference

Browsers treat the domain attribute independently of the port number; cookies set for localhost are sent to any service on the same host, regardless of port. This behavior contradicts the usual same‑origin policy for XHR, which considers port, but cookie handling follows the domain‑only rule.

Solution

Deploy services on different hosts (different IPs) so that the cookie domains differ, preventing unintended sharing. This avoids the problem but does not solve cases where cross‑domain interaction is required.

Limitations

The current workaround sidesteps the issue rather than addressing it. A more robust solution would involve configuring distinct cookie domains or paths, or using separate session stores to isolate sessions across ports.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaWeb DevelopmentTomcatSession ManagementCookieHttpSession
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.