
Web applications commonly need a way to end a session when the user has been inactive for a stretch of time. That reduces the risk of someone walking up to an unattended computer and gaining access to data they shouldn't see. In a Spring Boot project, the process is handled through session tracking, timeout limits, and the way the servlet container works alongside Spring Security.
I publish free articles like this daily, if you want to support my work and get access to exclusive content and weekly recaps, consider subscribing to my Substack.
Session Management Mechanics
Automatic logout after inactivity doesn't happen in isolation. The whole process begins with how Spring Boot handles sessions through its embedded servlet container. These containers, such as Tomcat, Jetty, or Undertow, manage the lifecycle of sessions, track the user's activity timestamps, and decide when a session is no longer valid. Spring Boot sits on top of that behavior and offers a simple way to configure it, while Spring Security adds another layer that ties sessions to authentication.
How HTTP Sessions Work
A session is created the moment a user first interacts with the application in a stateful way, such as logging in or submitting data that needs to persist across requests. That session is an object stored on the server, and it holds attributes like preference data and temporary values needed across multiple requests. With Spring Security, the session stores the SecurityContext for the authenticated user, not the user's password. To connect the browser with the session, the container sends back a cookie, usually named JSESSIONID, which acts as a pointer to the session object stored on the server.
What keeps the session alive is the activity timestamp. Every request that reaches the server updates the last accessed time on the session object. If a user stays active, the timestamp keeps moving forward. If they step away and don't send a request for long enough, the server eventually decides that the session is idle and discards it.
You can even inspect or manipulate this directly from code if needed:
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SessionInfoController {
@GetMapping("/session-info")
public String sessionInfo(HttpSession session) {
long creationTime = session.getCreationTime();
long lastAccessed = session.getLastAccessedTime();
return "Session created at: " + creationTime + " last accessed at: " + lastAccessed;
}
}This controller exposes the timestamps, which helps visualize how the session updates as requests are made. A user refreshing the endpoint will see the last accessed value advance with each call.
Configuring Session Timeout
Spring Boot makes it simple to decide how long a session should remain active without requests. The configuration happens at the container level and can be done through properties.
server:
servlet:
session:
timeout: 20mThat line sets the timeout to twenty minutes. From that point forward, any session with no activity for twenty minutes will be discarded. The invalidation doesn't occur at the twenty-minute mark on its own; it's enforced when the next request comes in and the server compares the current time with the stored last accessed time.
Developers can also change the interval on a per-session basis if needed:
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TimeoutController {
@GetMapping("/set-timeout")
public String setTimeout(HttpSession session) {
session.setMaxInactiveInterval(600); // 10 minutes
return "Session timeout updated to 10 minutes.";
}
}This method changes the timeout for just that session, which is useful when different users or areas of the application demand shorter or longer lifespans. Some sensitive features might require a quick expiration, while general browsing can allow more time.
Integration with Spring Security
Session expiration doesn't only impact stored attributes. With Spring Security in place, the session also carries authentication information through the security context. When a session expires, that authentication object is destroyed, and the user is effectively logged out. Any request for a protected endpoint will redirect the user to the login page, because from Spring Security's perspective, there's no valid identity anymore.
This is also where developers can define custom behavior. For idle timeouts, configure an invalid-session response. You can set an invalidSessionUrl for a redirect, or plug in an InvalidSessionStrategy to return JSON or other formats.
A simple configuration in a security setup can look somthing like this:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.sessionManagement(session -> session.invalidSessionUrl("/login?expired"));
return http.build();
}
}This configuration makes sure that any expired session will send the user back to the login page with an "expired" flag. It's simple but effective for cases where users need a prompt to log back in.
For more complex behavior, a custom handler can be registered:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfigInvalidSession {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.sessionManagement(sm -> sm
.invalidSessionUrl("/login?expired")
// or:
// .invalidSessionStrategy((req, res) -> {
// res.setContentType("application/json");
// res.getWriter().write("{\"error\":\"session expired\"}");
// })
);
return http.build();
}
}That custom strategy lets the developer fully control the flow when a session ends. Users might be sent to a dedicated timeout page, and the application can log the reason or even provide guidance on what happened.
Tracking Inactivity and Forcing Logout
Session timeout on its own covers many cases, but sometimes applications need more direct control over how inactivity is tracked and how logouts are handled. Spring Boot provides a base through the servlet container, and developers can extend that with custom filters, listeners, and Spring Security's session features.
Using a Servlet Filter for Idle Tracking
Servlet filters are often the most direct way to track requests and decide if a session should still be valid. They sit in the request chain and have access to both the incoming request and the current session if one exists. This makes them a natural place to check the time since the last activity.
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.IOException;
public class IdleTimeoutFilter implements Filter {
private static final int LIMIT = 900; // 15 minutes
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpSession session = request.getSession(false);
if (session != null) {
long now = System.currentTimeMillis();
long lastUsed = session.getLastAccessedTime();
if ((now - lastUsed) > LIMIT * 1000L) {
session.invalidate();
((HttpServletResponse) res).sendRedirect("/login?timeout");
return;
}
}
chain.doFilter(req, res);
}
}The filter checks each request against the idle threshold and forces a logout if the threshold has been crossed. A redirect sends the user back to the login page, which is usually the expected flow.
For applications with different areas that demand unique idle rules, the filter can include path checks. That way, a sensitive admin path could expire more quickly than general content:
if (session != null && request.getRequestURI().startsWith("/admin")) {
session.setMaxInactiveInterval(300); // 5 minutes for admin pages
}This makes the filter adaptable without having to apply one global timeout everywhere.
Configuring Session Events
Servlet containers fire lifecycle events whenever sessions are created or destroyed, and Spring Boot allows listeners to plug into those points. These listeners are useful when developers want to run logic at the moment of session expiration.
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
public class IdleSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent event) {
event.getSession().setMaxInactiveInterval(900); // 15 minutes
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
System.out.println("Session ended because of timeout or manual logout.");
}
}This listener both configures the timeout and records when sessions are destroyed. While simple, it can be extended to do more useful work such as writing audit logs or notifying external systems.
In some applications it makes sense to separate logging from the listener itself. A common method is to connect session events to a publisher so that multiple parts of the application can respond. That way, a session timeout could trigger an audit entry, a user notification, and a security event without overloading the listener with too much logic.
Spring Security Session Management Settings
Spring Security builds on top of the servlet container's session mechanics and adds its own features. A common extension is the ability to control concurrent sessions, which is important for applications that only allow one active session per user.
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.sessionManagement(session -> session
.invalidSessionUrl("/login?expired")
.maximumSessions(1) // concurrent session control
.expiredUrl("/login?expired") // triggers when a second login expires the first
);
return http.build();
}
}This configuration forces a logout if a second login occurs, and it also defines where users are redirected when their session has expired. It's a simple way to avoid confusion when the same account is being accessed from multiple devices.
Spring Security also provides strategies for more control over expired sessions. Developers can define a custom SessionInformationExpiredStrategy to handle advanced cases such as returning JSON responses for API clients or redirecting users to an error page with more detail.
Conclusion
Automatic logout after idle time in Spring Boot depends on several moving parts working in sequence. Servlet containers manage session objects and update timestamps with every request. Spring Boot adds an easy way to configure how long those sessions should last, while Spring Security ties authentication into the same lifecycle. Filters and listeners provide extra control for developers who need to track activity in more detail, and security strategies decide what happens once a session is no longer valid. All of these pieces fit into place so that inactivity is monitored, timeouts are enforced, and expired sessions are removed in a consistent way.
- Spring Boot Reference Documentation
- Spring Security Reference Documentation
- Servlet Specification Documentation
Thanks for reading! If you found this helpful, highlighting, clapping, or leaving a comment really helps me out.
