skip to content

Why does Spring Session let you run multiple stateless app instances behind a load balancer, and what problem does it solve?

level: juniorimportance: must knowfreq 62%

answer

  1. HttpSession = one JVM's memory
  2. External shared store = any instance serves any request
  3. SessionRepositoryFilter swaps getSession()
  4. @EnableRedisHttpSession / starter
  5. Kills sticky sessions

basics

~20 s

By default an HTTP session lives in one server's memory, so a user must stick to that server. Spring Session stores sessions in a shared external store (Redis, JDBC), so any instance can read them.

solid answer

~40 s

A standard servlet HttpSession is held in the memory of the JVM that created it. With several app instances behind a load balancer, a request routed to a different instance won't find that session — you'd need sticky sessions, and losing an instance loses its sessions. Spring Session replaces the container's HttpSession with an implementation backed by an external store (Redis, Hazelcast, JDBC/database, MongoDB). All instances read/write the same store, so any instance can serve any request and the app becomes truly stateless at the JVM level. This enables horizontal scaling, rolling deploys, and failover without losing logged-in users. You enable it with @EnableRedisHttpSession (or @EnableJdbcHttpSession, etc.), and a SessionRepositoryFilter transparently swaps the session implementation.

code

java · 18 lines
java
@Configuration
@EnableRedisHttpSession // externalize HttpSession into Redis
public class SessionConfig {
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(
            new RedisStandaloneConfiguration("redis", 6379));
    }
}

// Controller code is unchanged — getSession() now hits Redis via the filter
@GetMapping("/visit")
public int visit(HttpSession session) {
    Integer n = (Integer) session.getAttribute("count");
    n = (n == null) ? 1 : n + 1;
    session.setAttribute("count", n); // written to shared store
    return n;
}

go deeper

for a junior

Know the core idea: sessions normally live in one server's memory; Spring Session puts them in a shared store so all instances can read them.

for a middle

Should name a backend (Redis/JDBC), the enabling annotation, and that SessionRepositoryFilter transparently swaps the session.

for a senior

Discuss serialization concerns, store HA, latency trade-offs, and Spring Security SecurityContext living in the session.

for a principal

Weigh session-in-store vs stateless-token architectures, store failure modes, and operational impact on deploys/autoscaling.

## The core problem A classic servlet **HttpSession** is an in-memory map living inside a single JVM (the app server / container). The server hands the browser a **session ID** in a cookie (`JSESSIONID` by default), and on each request looks that ID up in *its own* memory to restore the session. With one server this is fine. With **multiple instances behind a load balancer** it breaks: if instance A created the session but the load balancer routes the next request to instance B, B has no such session in memory and the user appears logged out. ### Traditional workarounds (and why they're weak) - **Sticky sessions (session affinity):** the load balancer pins each user to the instance that created their session. Works, but: losing an instance loses all its sessions; uneven load; and it complicates rolling deploys and autoscaling. - **Container-level replication:** app servers gossip session state to each other. Chatty, memory-heavy, and coupled to a specific server. ## What Spring Session does **Spring Session** externalizes session state into a **shared datastore** so every instance sees the same sessions. It does this without you rewriting code that calls `HttpServletRequest.getSession()`. Key moving parts: - **`SessionRepositoryFilter`** — a servlet `Filter` registered early in the chain. It wraps the incoming request so that any call to `getSession()` returns a Spring-managed session instead of the container's. This is the magic that makes the swap transparent. - **`SessionRepository<S>`** — the abstraction for load/save/delete of sessions. Concrete backends: `RedisSessionRepository` / `RedisIndexedSessionRepository`, `JdbcIndexedSessionRepository`, `HazelcastIndexedSessionRepository`, `MongoIndexedSessionRepository`. - **`Session`** — Spring's session interface (implemented by `MapSession`, `RedisSession`, etc.). - **Enabling annotations:** `@EnableRedisHttpSession`, `@EnableJdbcHttpSession`, `@EnableHazelcastHttpSession`, `@EnableMongoHttpSession` — or in Spring Boot, just add the starter (e.g. `spring-session-data-redis`) and set `spring.session.store-type` if needed; Boot auto-configures it. ## Why this yields "stateless" instances The JVM no longer *owns* any session data — it's just a stateless compute node reading/writing the shared store. That unlocks: - **Horizontal scaling:** add/remove instances freely; no sticky sessions required. - **Failover:** an instance can die mid-session; another serves the next request. - **Rolling / blue-green deploys:** restart instances without logging everyone out (as long as the store survives). ## Gotchas - **Serialization:** attributes you put in the session must be serializable to the store (Java serialization or JSON, depending on config). Non-serializable attributes will fail. - **The store becomes a dependency and a single point of failure** — it must itself be HA (e.g. Redis cluster/sentinel). - **Latency:** every session read/write is now a network hop, not a memory lookup. - **Spring Security integration:** Spring Security stores the `SecurityContext` in the session, so externalizing the session is exactly what makes clustered authentication work. ## When to use Any time you run more than one instance and keep server-side session state (logins, carts, CSRF tokens). If you're fully token-based/stateless (e.g. JWT with no server session), you may not need it at all.

  • Do you still need sticky sessions when using Spring Session with Redis?
    No — that's the point. Since every instance reads the same store, the load balancer can route freely. Sticky sessions can still slightly help latency/cache locality but aren't required for correctness.
  • What must be true of objects you store as session attributes?
    They must be serializable to the backing store's format (Java Serializable for default JDK serialization, or JSON-mappable if you configure a JSON serializer). Non-serializable attributes cause save failures.

saying these in an interview costs you the question

  • Thinking Spring Session requires code changes to every getSession() call (it doesn't — the filter handles it)
  • Believing the external store removes the need for it to be highly available
  • Confusing this with JWT/stateless tokens — Spring Session is still server-side session state

context