skip to content

What is the difference between maxSessionsPreventsLogin(true) and (false)?

level: middleimportance: must knowfreq 55%

answer

  1. false = newest wins, oldest expired
  2. true = oldest wins, new login rejected
  3. true throws SessionAuthenticationException
  4. true + stale registry = lockout risk
  5. limit is per-principal (equals/hashCode)

basics

~20 s

With false (default) a new login is allowed and the oldest session is expired. With true the new login is refused (throws SessionAuthenticationException) and existing sessions stay alive — old sessions win over new ones.

solid answer

~40 s

`maxSessionsPreventsLogin` decides who loses when a user exceeds `maximumSessions`. Default `false`: the new authentication succeeds and Spring's `ConcurrentSessionControlAuthenticationStrategy` marks the oldest session's `SessionInformation` as expired; that session is torn down by `ConcurrentSessionFilter` on its next request ("kick the old device"). With `true`: the strategy throws `SessionAuthenticationException` at login time, so the new attempt is rejected and current sessions are untouched ("first-in wins"). Choose `true` for strict single-seat licensing where you never want an active user silently kicked; choose `false` for consumer UX where the latest device should just take over. A caveat with `true`: if a previous session is still counted (e.g. the registry wasn't told it was destroyed), a legitimate user can be locked out — which is why correct `HttpSessionEventPublisher` wiring matters.

code

java · 9 lines
java
http.sessionManagement(session -> session
    .maximumSessions(1)
    .maxSessionsPreventsLogin(true)   // reject the new login instead of
                                      // expiring the existing session
    .expiredUrl("/login?expired"));

// With true, a second concurrent login triggers:
//   org.springframework.security.web.authentication.session
//     .SessionAuthenticationException: "Maximum sessions of 1 ... exceeded"

go deeper

for a junior

Know false = new login wins, true = new login blocked.

for a middle

Explain the mechanism: expireNow vs SessionAuthenticationException, and the lazy expiry on next request.

for a senior

Tie the true-mode lockout risk to HttpSessionEventPublisher and registry accuracy; discuss per-principal equals/hashCode.

for a principal

Weigh UX and security trade-offs, and note both modes depend on a reliable, ideally shared, SessionRegistry in a cluster.

**The setting.** `maxSessionsPreventsLogin` is a boolean on `sessionManagement().maximumSessions(n)`. It only matters when a login would push a principal *over* the `maximumSessions` limit. **`false` — the default (newest wins).** - The login is allowed to proceed. - `ConcurrentSessionControlAuthenticationStrategy` finds the principal's least-recently-used session in the `SessionRegistry` and calls `SessionInformation.expireNow()`, flipping its `expired` flag to true. - The old session is *not* immediately invalidated server-side; it dies lazily. On that old session's **next request**, `ConcurrentSessionFilter` sees `sessionInformation.isExpired()`, invalidates the `HttpSession`, runs the logout handlers, and redirects to the configured `expiredUrl` (or writes a default expired message). - User-visible effect: logging in on a new device silently boots the oldest device. **`true` (oldest wins / login prevented).** - At login, `ConcurrentSessionControlAuthenticationStrategy` throws `SessionAuthenticationException` ("Maximum sessions of N for this principal exceeded"). - The new authentication fails; existing sessions are untouched. - User-visible effect: the second device sees a login error until an existing session logs out or times out. **Why the wiring matters more with `true`.** The decision is based on the *count in the registry*. If a session ended (browser closed, timeout, logout) but the registry was never notified, its stale entry still counts toward the limit. With `true` that stale count can lock a legitimate user out entirely. The fix is registering `HttpSessionEventPublisher` so `HttpSessionDestroyedEvent` removes ended sessions from `SessionRegistryImpl`. With `false` a stale entry is less harmful because the old (possibly dead) session just gets expired. **Edge cases.** - The limit is per-principal, keyed by the principal's `equals`/`hashCode`. If your custom principal object doesn't implement these correctly, counting breaks. - With `true`, there is no "queue": it's a hard rejection, not a wait. - Setting `maximumSessions` at all activates the concurrency filter/strategies even if you leave `maxSessionsPreventsLogin` at default. **When to use which.** `true`: strict license seats, security-sensitive apps that must never silently drop an authenticated session, admin consoles. `false`: consumer apps where 'log in on my new phone and it just works' is the expected UX.

  • With maxSessionsPreventsLogin(true), how can a legitimate user get wrongly locked out, and how do you prevent it?
    If a prior session ended but the SessionRegistry still counts it (no destroy event), the count stays at the cap. Registering HttpSessionEventPublisher so HttpSessionDestroyedEvent purges the registry prevents this.
  • Is the old session invalidated immediately when maxSessionsPreventsLogin is false?
    No. It is only flagged expired in the registry; ConcurrentSessionFilter invalidates it lazily on that session's next request.

saying these in an interview costs you the question

  • Saying true expires the old session (it actually blocks the new login)
  • Claiming the old session is killed instantly rather than on its next request
  • Not knowing which is the default (false is default)

context