Java Servlet Remember Me: Fix Null Session in Filter
Implement stay logged in with session cookies and DB in Java servlets. Fix null session in servlet filter after login forward using getSession(true), REQUEST dispatchers, and secure cookie session ID practices.
How to implement ‘remember me’ or ‘stay logged in’ functionality in a plain Java Servlet application (no Spring or Shiro) using cookies and a database? Why does the session become null in the filter after forwarding from the login servlet?
I’m following this Stack Overflow thread (BalusC’s solution 2 for Java 6/7) but encountering issues with the filter being called multiple times during login.
Problem Flow:
- User requests protected resource (
/jsp/startup.jsp): Filter called, session isnull, no cookie/DB entry → forward tologin.jsp. - User submits login form: Filter detects
usernameparameter, callschain.doFilter()toProcessLoginservlet (session now non‑null). ProcessLoginservlet:
- If
remember_mechecked: Generates UUID, adds cookie, stores user in DB with UUID. - Calls
request.login(username, password). - Sets
userattribute in session. - Forwards to original
startup.jsp.
- Forward triggers filter third time: Session is unexpectedly
nullagain, breaking logic.
Key Code Snippets:
MyServletFilter.java (extends SecurityParentServlet):
public class MyServletFilter extends SecurityParentServlet implements Filter {
protected static final Logger logger = Logger.getLogger(MyServletFilter.class);
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain fchain)
throws IOException, ServletException {
logger.debug("MyServletFilter was called");
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String sUserName = request.getParameter("username");
if ((sUserName != null) && (sUserName.length() > 0)) {
fchain.doFilter(req, resp);
return;
}
HttpSession session = request.getSession(false);
if ((session != null) && (session.getAttribute("user") != null)) {
fchain.doFilter(req, resp);
return;
} else {
String uuid = getCookieValue(request, COOKIE_NAME);
logger.debug("MyServletFilter cookie value = " + uuid);
RememberMeUser rmUser = null;
if (uuid != null) {
rmUser = DBHandler.getRememberUser(uuid);
if (rmUser != null) {
request.login(rmUser.getAccount(), rmUser.getPassword());
session.setAttribute("user", rmUser);
addCookie(response, uuid);
logger.debug("User is " + request.getRemoteUser());
} else {
removeCookie(response);
}
}
if (rmUser == null) {
goToLogin(request, response);
} else {
fchain.doFilter(req, resp);
}
}
}
}
ProcessLogin.java (@WebServlet("/ProcessLogin")):
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String sUserName = request.getParameter("username");
String sPassword = request.getParameter("password");
String sRememberMe = request.getParameter("remember_me");
RememberMeUser rmUser = new RememberMeUser();
if (sRememberMe != null) {
String uuid = UUID.randomUUID().toString();
rmUser.setUniqueId(uuid);
addCookie(response, uuid);
if (DBHandler.isRememberUserInDB(sUserName))
DBHandler.updateRememberMeUser(sUserName, uuid, sPassword);
else
DBHandler.insertRememberMeUser(uuid, sUserName, sPassword);
} else {
removeCookie(response);
DBHandler.deleteRememberMeUser(sUserName);
}
try {
request.login(sUserName, sPassword);
rmUser.setAccount(sUserName);
HttpSession session = request.getSession(false);
session.setAttribute("user", rmUser);
forward(request, response, "index");
} catch (Exception e) {
logger.error("Exception trying to login " + sUserName, e);
goToLogin(request, response);
}
}
SecurityParentServlet provides addCookie, removeCookie, getCookieValue, forward, goToLogin, and jSecurityCheck (not fully shown, uses programmatic POST to /j_security_check).
What causes the session to become null after the forward? How to fix the filter logic for proper ‘remember me’ handling in servlets?
Implementing “remember me” or stay logged in functionality in a plain Java servlet request handler relies on session cookies paired with a database to store secure tokens, keeping users authenticated across browser restarts without re-entering credentials. The session turns null in your servlet filter after forwarding from ProcessLogin because request.login() invalidates the existing session to prevent fixation attacks, getSession(false) won’t recreate it, and the FORWARD dispatcher re-triggers the filter on the JSP. To fix this, switch to getSession(true), limit the filter to REQUEST dispatches only, use sendRedirect post-login instead of forward, and adopt dual-token security (selector + hashed validator) for robust cookie session ID handling.
Contents
- Understanding Remember Me in Java Servlet Request Handling
- Why Your Session Goes Null in the Servlet Filter After Forward
- Root Causes: Dispatchers, request.login(), and getSession Pitfalls
- Complete Stay Logged In Implementation with Session Cookies and DB
- Fixing MyServletFilter Logic for Reliable Remember Me
- Secure Cookie Session ID Best Practices
- Updating ProcessLogin Servlet and Alternatives
- Testing, Debugging, and Common Errors
- Sources
- Conclusion
Understanding Remember Me in Java Servlet Request Handling
Ever hit a wall trying to make “remember me” work smoothly in a vanilla servlet app? You’re not alone—it’s a classic pain point with java servlet request flows, especially without Spring’s hand-holding. The idea is simple: on login, if the checkbox is ticked, generate a secure token, stash it in a long-lived cookie (say, 7-30 days), and mirror it in your database tied to the user. Next visit, the servlet filter sniffs the cookie, validates against the DB, logs them in programmatically via request.login(), and sets a session attribute to bypass future checks.
BalusC nailed this in his time-tested Stack Overflow answer for Java 6/7 servlets: filter first on params/session, fallback to cookie/DB, then chain. But your flow breaks because forwards loop back through the filter unexpectedly. Why? Servlet dispatchers. More on that soon.
Picture the happy path:
- No session/cookie? Redirect to login.
- Cookie valid?
request.login()+ session attr = proceed. - Logs out? Nuke cookie and DB token.
This beats session-only logins, which die on browser close. But security matters—raw UUIDs invite hijacking. We’ll upgrade to pro-level tokens later.
Why Your Session Goes Null in the Servlet Filter After Forward
Let’s replay your exact problem flow with logs in mind. User hits /jsp/startup.jsp: filter fires (session null, no cookie), forwards to login. Form posts to ProcessLogin: filter sees “username” param, chains through—session exists now. ProcessLogin generates UUID cookie if “remember_me”, calls request.login(), sets session “user”, forwards back to startup.jsp.
Boom—filter #3: session null again! Logger screams “MyServletFilter was called” endlessly? That’s the forward dispatcher re-entering the filter chain for JSPs mapped to *.jsp. Your getSession(false) chokes because request.login() zapped the session for anti-fixation protection—containers like Tomcat invalidate on programmatic login to dodge session fixation exploits.
You see the ripple: forward preserves the request/session context but re-runs filters (unless restricted), post-invalidation getSession(false) returns null, cookie check happens too late or fails, loop to login. Frustrating, right? But fixable without frameworks.
Root Causes: Dispatchers, request.login(), and getSession Pitfalls
Drill down—three culprits stack up here.
First, dispatcher types. Filters default to all: REQUEST, FORWARD, INCLUDE, etc. Your *.jsp mapping catches forwards from ProcessLogin, re-filtering the same request. Solution? <dispatcher>REQUEST</dispatcher> in web.xml or @WebFilter(dispatcherTypes = DispatcherType.REQUEST).
Second, request.login() nukes the session. Per Servlet spec and Baeldung’s breakdown, it invalidates to block fixation (attacker steals old session ID pre-login). Post-call, getSession(false) fails—use getSession(true) to force a new one.
Third, param/session/cookie order. Your filter checks params first (good for login POST), but after forward, params vanish (FORWARD copies attributes, not params). Cookie logic runs, but null session blocks session.setAttribute("user").
Bonus pitfall from Coderanch forums: getSession(false) is brittle post-any-invalidation. Always true after login. And forwards? Swap to sendRedirect—new REQUEST, no filter loop, session cookie travels.
Quick test: Add logger.debug("Dispatcher: " + request.getDispatcherType());—you’ll see FORWARD on the third call.
Complete Stay Logged In Implementation with Session Cookies and DB
Time for working code. We’ll hybrid BalusC with CodeJava’s secure dual-token approach: random selector (cookie) + hashed validator (DB). On use, rotate both for replay protection. DB schema first (MySQL/Postgres):
CREATE TABLE remember_me_tokens (
username VARCHAR(100) NOT NULL,
selector VARCHAR(32) BINARY NOT NULL UNIQUE,
validator_hash CHAR(64) NOT NULL,
password_hash VARCHAR(255), -- Optional, for login if needed
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_used_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (selector)
);
Constants (in SecurityParentServlet or util):
private static final String COOKIE_NAME = "rememberMe";
private static final int COOKIE_MAX_AGE = 60 * 60 * 24 * 30; // 30 days
private static final String COOKIE_PATH = "/";
addCookie (enhanced):
protected void addCookie(HttpServletResponse response, String selector) {
Cookie cookie = new Cookie(COOKIE_NAME, selector);
cookie.setPath(COOKIE_PATH);
cookie.setMaxAge(COOKIE_MAX_AGE);
cookie.setHttpOnly(true);
cookie.setSecure(request.isSecure()); // HTTPS only in prod
response.addCookie(cookie);
}
getCookieValue (same, trim nulls).
DBHandler stubs (use BCrypt/PBKDF2 for hashes):
public static RememberMeUser getRememberUser(String selector) {
// Query by selector, verify validator_hash == hash(provided_validator)
// Rotate: new selector/validator, update DB
}
public static void insertRememberMeUser(String selector, String username, String plainValidator, String password) {
String validatorHash = hash(plainValidator); // BCrypt
// INSERT with hashed validator
}
public static void updateRememberMeUser(String username, String selector, String plainValidator) {
// UPDATE selector/validator_hash, last_used_at
}
Full flow later in fixes.
Fixing MyServletFilter Logic for Reliable Remember Me
Here’s your revised MyServletFilter. Key changes: @WebFilter(dispatcherTypes=DispatcherType.REQUEST), getSession(true) post-login, param check last (login POST needs it), early cookie/DB before session attr.
@WebFilter(urlPatterns = {"/*"}, dispatcherTypes = DispatcherType.REQUEST) // Key: REQUEST only!
public class MyServletFilter extends SecurityParentServlet implements Filter {
private static final Logger logger = Logger.getLogger(MyServletFilter.class);
private static final String COOKIE_NAME = "rememberMe";
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
logger.debug("Filter called for: " + ((HttpServletRequest) req).getRequestURI());
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
// 1. Check session first (fastest)
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("user") != null) {
chain.doFilter(req, resp);
return;
}
// 2. Check cookie/DB remember me
String selector = getCookieValue(request, COOKIE_NAME);
if (selector != null) {
RememberMeUser rmUser = DBHandler.getRememberUser(selector); // Validates & rotates
if (rmUser != null) {
try {
request.login(rmUser.getAccount(), rmUser.getPassword());
session = request.getSession(true); // Critical: true post-login!
session.setAttribute("user", rmUser);
addCookie(response, rmUser.getNewSelector()); // Rotate
logger.debug("Remember me login for: " + rmUser.getAccount());
chain.doFilter(req, resp);
return;
} catch (ServletException e) {
logger.warn("Invalid remember token", e);
removeCookie(response);
}
} else {
removeCookie(response);
}
}
// 3. Check login params LAST (for ProcessLogin POST)
String username = request.getParameter("username");
if (username != null && !username.isEmpty()) {
chain.doFilter(req, resp); // Let ProcessLogin handle
return;
}
// Fail: to login
goToLogin(request, response);
}
}
No more forward loops. Session survives.
Secure Cookie Session ID Best Practices
Raw UUID? Meh—predictable. Dual-token shines: cookie holds opaque selector (random 16-32 chars), DB has matching hashed validator (random bytes, BCrypt’d). On read, app generates validator, hashes, compares. Rotate both on use/extract.
Per Servlet API Cookie docs:
HttpOnly=true: JS-proof.Secure=true: HTTPS-only.SameSite=Strict/Lax: CSRF hedge.- Path=“/”, no Domain unless subdomains.
Logout: DBHandler.deleteBySelector(selector); removeCookie(); session.invalidate();.
Hash impl (use BouncyCastle or java.security):
private String hash(String input) {
return BCrypt.hashpw(input, BCrypt.gensalt());
}
30-day expiry? Fine-tune via DB last_used_at + cron purge old tokens.
Updating ProcessLogin Servlet and Alternatives
Your ProcessLogin needs tweaks: getSession(true) post-login, sendRedirect over forward (new request, cookie sticks).
@WebServlet("/ProcessLogin")
public class ProcessLogin extends SecurityParentServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { // POST for forms!
String username = request.getParameter("username");
String password = request.getParameter("password");
String rememberMe = request.getParameter("remember_me");
if (rememberMe != null) {
String selector = randomSelector(); // 32 hex chars
String validator = randomValidator(); // 32 bytes base64
DBHandler.insertRememberMeUser(selector, username, validator, hash(password));
addCookie(response, selector);
} else {
DBHandler.deleteRememberMeUser(username);
removeCookie(response);
}
try {
request.login(username, password);
HttpSession session = request.getSession(true); // Recreates if needed
session.setAttribute("user", new RememberMeUser(username));
String target = request.getParameter("target") != null ?
request.getParameter("target") : "/jsp/startup.jsp";
response.sendRedirect(target); // New REQUEST—no filter loop!
} catch (Exception e) {
logger.error("Login failed for " + username, e);
goToLogin(request, response);
}
}
}
Alt: request.changeSessionId() (Servlet 3.1+) instead of login invalidation. Or j_security_check POST via util.
Login.jsp: <input type="hidden" name="target" value="${param.target}"> for original URI.
Testing, Debugging, and Common Errors
Test rigorously:
- Login w/o remember: close browser, reload → login prompt.
- With remember: close/reopen → direct to protected, check logs/cookie.
- Invalid cookie: tamper selector → delete on fail.
- Tools: Chrome DevTools (Application > Cookies), Postman w/ cookies.
Debug tips:
- Log dispatcher/request URI/session ID.
- Wireshark for cookie headers.
- Common gotchas:
getSession(false)everywhere (swap to true), no@WebFilterdispatcher, HTTPS=false in prod.
Edge: Multi-tab? Token race—use atomic DB updates. Mobile? Shorten expiry.
If Tomcat: session cookie JSESSIONID auto-httpOnly since 7.0.
Sources
- How to implement stay logged-in when user login in to the web application — BalusC’s Solution 2 for servlet remember me with filter/cookie/DB: https://stackoverflow.com/questions/5082846/how-to-implement-stay-logged-in-when-user-login-in-to-the-web-application/5083809
- How to implement Remember Password (Remember Me) for Java web application — Secure dual-token implementation with DB schema and rotation: https://www.codejava.net/coding/how-to-implement-remember-password-remember-me-for-java-web-application
- Guide to HttpServletRequest.getSession() — Explains getSession(false/true) behavior post-login invalidation: https://www.baeldung.com/java-request-getsession
- request.getSession(false) working intermittently — Forum discussion on session null after invalidate: https://coderanch.com/t/358332/java/request-getSession-false-working
- Cookie (Jakarta Servlet API) — Official docs for secure cookie flags like HttpOnly/Secure: https://jakarta.ee/specifications/servlet/4.0/apidocs/javax/servlet/http/cookie
Conclusion
Nail “remember me” in java servlet request apps by securing session cookies with DB tokens, fixing filter loops via REQUEST dispatchers and getSession(true), and redirecting post-login for clean flows. Your null session vanishes with these tweaks—deploy, test browser restarts, and enjoy stay logged in that just works. Prioritize hashing/rotation for prod; it’ll handle real traffic without hiccups.