Reduce unnecessary JSESSIONID session cookies on anonymous/public page requests
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
This needs a sanity check. The goal is to behave and perform better behind CDNs.
Summary
Several code paths in the filter chain and visitor tracking system create HTTP sessions (and thus send JSESSIONID cookies) for every anonymous page request, even when no session state is needed. This increases memory pressure on the session store (especially with Redis session replication), inflates cookie headers on every response, and undermines cacheability of public pages.
Cookies involved
| Cookie | Type | Set by | Issue |
|---|---|---|---|
JSESSIONID |
Tomcat session cookie | Tomcat container, triggered by any request.getSession() call |
Created for every anonymous page view by multiple code paths (findings 1–5 below) |
opvc |
dotCMS session cookie | NumberOfTimeVisitedCounter → response.addCookie() |
Set on every first page view per browser session, no opt-out (finding 6) |
sitevisitscookie |
dotCMS persistent cookie (5 years) | NumberOfTimeVisitedCounter → response.addCookie() |
Set/updated on every first page view, no opt-out (finding 6) |
Note: The dmid (long-lived visitor ID) cookie and rme (JWT remember-me) cookie are not affected — dmid is created in CookieUtil.createCookie() but the call in ClickstreamFactory.addRequest() (line 156) discards the return value without adding it to the response, and rme is only set on explicit "remember me" login.
Problem
A typical anonymous page view currently triggers JSESSIONID session cookie creation from multiple independent code paths, and additionally receives opvc + sitevisitscookie tracking cookies:
1. CMSFilter.countPageVisit() → VisitorAPIImpl.getVisitor(request) — highest impact
Files: CMSFilter.java:235, VisitorAPIImpl.java:52,70
CMSFilter.countPageVisit() calls visitorAPI.getVisitor(request) with no create argument. This defaults to CREATE_VISITOR_OBJECT_IN_SESSION=true, which calls request.getSession() — creating a new session and JSESSIONID for every anonymous visitor.
// CMSFilter.java:235
Optional<Visitor> visitor = visitorAPI.getVisitor(request); // defaults create=true
// VisitorAPIImpl.java:70
final HttpSession session = request.getSession(); // creates session unconditionally
2. BaseCharacter visitor logging creates sessions for analytics
File: BaseCharacter.java:70-71
The visitor logging system (runs when license >= STANDARD) calls request.getSession().getId() just to log the session ID — creating a session as a side effect.
myMap.get().put("session", request.getSession().getId()); // line 70
myMap.get().put("sessionNew", request.getSession().isNew()); // line 71
3. RulesEngineCharacter and GDPRCharacter use getSession() without false
Files: RulesEngineCharacter.java:23-24, GDPRCharacter.java:30-31,59-60
Both call request.getSession() to check for session attributes, creating sessions even when they just need to read (and could safely get null).
// RulesEngineCharacter.java:23
if (request.getSession().getAttribute(WebKeys.RULES_ENGINE_FIRE_LIST) != null) {
// GDPRCharacter.java:30
String user = (request.getSession().getAttribute(WebKeys.CMS_USER) != null)
4. ClickstreamFactory.addRequest() forces session creation
File: ClickstreamFactory.java:74-76
HttpSession session = request.getSession();
Clickstream clickstream = (Clickstream) request.getSession(true).getAttribute("clickstream");
Creates a session and stores clickstream data in it. While ClickstreamFilter itself is commented out/deprecated, ClickstreamFactory.addRequest() may still be invoked from other paths.
5. LanguageWebAPIImpl creates sessions on language change
File: LanguageWebAPIImpl.java:134
When a language_id parameter is present and differs from the current language, a session is created to persist the language preference — even for anonymous visitors.
sessionOpt = httpRequest.getSession(true); // creates session on language change
6. Tracking cookies set on every first page view
Files: NumberOfTimeVisitedCounter.java, CookieUtil.java
CMSFilter.countSiteVisit() calls NumberOfTimeVisitedCounter.maybeCount() for all LIVE page requests, which sets:
opvc— session cookie (once-per-visit marker)sitevisitscookie— 5-year persistent cookie (visit counter)
These are unconditionally added for every new anonymous visitor with no opt-out mechanism.
Code paths doing it correctly (for reference)
CharsetEncodingFilter— usesgetSession(false)✅TimeMachineFilter— checksgetSession(false)before proceeding ✅HealthProbeServlet— no session creation ✅VisitorAPIImpl.getVisitor(request, false)— correctly usesgetSession(false)✅ (but nobody calls it withfalsefrom the filter chain)
Suggested Resolution
A. Guard session creation in CMSFilter.countPageVisit()
Change to use getVisitor(request, false) so a Visitor/session is not created if one doesn't already exist:
private void countPageVisit(HttpServletRequest request) {
PageMode mode = PageMode.get(request);
if (mode == PageMode.LIVE) {
Optional<Visitor> visitor = visitorAPI.getVisitor(request, false);
visitor.ifPresent(v -> v.addPagesViewed(request.getRequestURI()));
}
}
Alternatively, make CREATE_VISITOR_OBJECT_IN_SESSION default to false and let deployments that need visitor tracking opt in.
B. Fix BaseCharacter to use getSession(false)
HttpSession session = request.getSession(false);
myMap.get().put("session", session != null ? session.getId() : "none");
myMap.get().put("sessionNew", session != null && session.isNew());
C. Fix RulesEngineCharacter and GDPRCharacter
Replace all request.getSession().getAttribute(...) calls with null-safe getSession(false) patterns:
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute(WebKeys.RULES_ENGINE_FIRE_LIST) != null) {
// ...
}
D. Make visitor tracking cookies configurable
Add a config property (e.g., ENABLE_VISITOR_TRACKING_COOKIES=true) to gate NumberOfTimeVisitedCounter.maybeCount(), allowing deployments to disable anonymous tracking cookies entirely.
E. Guard LanguageWebAPIImpl session creation
Store language preference in a cookie instead of creating a session, or only persist to session if one already exists.
Impact
- Reduces session store memory usage for sites with high anonymous traffic
- Eliminates unnecessary
Set-Cookie: JSESSIONIDheaders on public page responses - Improves CDN/proxy cacheability of anonymous page responses
- Reduces tracking cookie exposure for GDPR/privacy compliance
Environment
All versions — these patterns have been present for a long time.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with CMSFilter.countPageVisit() and trace the session behavior through VisitorAPIImpl.java, BaseCharacter.java, RulesEngineCharacter.java, GDPRCharacter.java, ClickstreamFactory.java, LanguageWebAPIImpl.java, NumberOfTimeVisitedCounter.java, and CookieUtil.java. Review the existing getSession(false) paths and determine the intended scope before choosing among the suggested resolutions. Done means the selected anonymous request paths no longer create unnecessary sessions or tracking cookies, with behavior verified across the affected entry points.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- backend, performance
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 32/100