๐Ÿ”ด The Error You're Seeing

Confirm this matches your console output. If it does, you're in the right place.

ERROR LOGorg.springframework.security.web.authentication.session.SessionAuthenticationException: This session has been expired (possibly due to multiple concurrent logins being attempted as the same user) at org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy.onAuthentication(ConcurrentSessionControlAuthenticationStrategy.java:156)

โšก Quick Fix Works 80% of the time

Increase the maximumSessions in your SessionManagement configuration, or set maxSessionsPreventsLogin to true.

http.sessionManagement(session -> session .maximumSessions(10) .maxSessionsPreventsLogin(false) );

๐Ÿง  Why this Happens

Tap to expand the deep technical explanation

You configured Spring Security to limit the number of concurrent active sessions a user can have (e.g., `maximumSessions(1)`). The user logged in from Chrome, and then logged in again from Firefox. Spring Security detected the second login and expired the first session, throwing this exception when the first browser tries to make a new request.

The HITEC City Parking Spot Analogy:

It's like a gym that only allows one active membership card per person. If you get a new card, the old card is immediately deactivated and snapped in half the next time you try to swipe it.

๐Ÿ” How to Reproduce Confirm this is your error

Configure `http.sessionManagement(s -> s.maximumSessions(1))`. Log in as 'admin' in Chrome. Log in as 'admin' in Firefox. Go back to Chrome and click any link. The Chrome session will crash with this error.

๐Ÿ› ๏ธ Solutions (5 Ways to Fix)

Solution 1โœ“ Most common cause

Increase maximumSessions limit

๐Ÿ‘‰ Use this if you want to allow users to be logged in from multiple devices.

Set the limit to a higher number (or -1 for unlimited) to prevent the session from being invalidated.

@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.sessionManagement(session -> session .maximumSessions(3) // Allow 3 concurrent devices ); return http.build(); }
Solution 2

Prevent the second login instead

๐Ÿ‘‰ Use this if you strictly want to block users from sharing accounts.

Set `maxSessionsPreventsLogin(true)`. The second login attempt will be rejected, and the first session remains active.

http.sessionManagement(session -> session .maximumSessions(1) .maxSessionsPreventsLogin(true) // Block second login );
Solution 3

Register SessionRegistry bean

๐Ÿ‘‰ Use this if your session management config is being ignored.

Spring Security needs a SessionRegistry to track active sessions. If it's missing, concurrent login control silently fails or behaves erratically.

@Bean public SessionRegistry sessionRegistry() { return new SessionRegistryImpl(); } // Inject it into your config: http.sessionManagement(session -> session .maximumSessions(1) .sessionRegistry(sessionRegistry()) );
Solution 4

Add HttpSessionEventPublisher

๐Ÿ‘‰ Use this if expired sessions aren't being cleaned up from the registry.

Spring needs to know when a session dies to remove it from the active list. This listener does exactly that.

@Bean public HttpSessionEventPublisher httpSessionEventPublisher() { return new HttpSessionEventPublisher(); }
Solution 5

Handle the exception in UI/API

๐Ÿ‘‰ Use this to show a clean 'Session Expired' page instead of a 500 error.

Configure an InvalidSessionHandler to redirect the user to the login page with a message.

http.sessionManagement(session -> session .invalidSessionUrl("/login?expired=true") );

๐Ÿ“‹ Version Notes

Spring Boot 2.x

Session management configured via WebSecurityConfigurerAdapter.

Spring Boot 3.x

Configured via SecurityFilterChain lambda DSL.

๐Ÿ›ก๏ธ How to Prevent This Next Time

If using stateless JWTs, you don't need session management at all. Set `SessionCreationPolicy.STATELESS` to avoid this entire class of errors.

Course Search
Search across all chapters & stages
๐Ÿ“–

Search the course

Type any topic โ€” branching, stash, rebase, hooks โ€” and jump straight to that chapter.

merge branchesgit stashundo commitrebase