Spring Session - Quick Reference

SkillDatabases & data

Spring Session for distributed session management with Redis, JDBC, or Hazelcast. Covers session configuration, security integration, and session events.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Spring Session - Quick Reference skill

What this skill tells your AI

The instructions your AI receives, as published by claude-dev-suite/claude-dev-suite in skills/backend-frameworks/spring-session/SKILL.md and read by ahel’s review.

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: spring-session for comprehensive documentation.

Dependencies

<!-- Redis Backend -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- JDBC Backend -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-jdbc</artifactId>
</dependency>

<!-- Hazelcast Backend -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-hazelcast</artifactId>
</dependency>

Architecture

┌─────────────────────────────────────────────────────────────┐
│                    Load Balancer                            │
└─────────────────────────────────────────────────────────────┘
                            │
        ┌───────────────────┼───────────────────┐
        ▼                   ▼                   ▼
┌───────────────┐   ┌───────────────┐   ┌───────────────┐
│   Instance 1  │   │   Instance 2  │   │   Instance 3  │
│   App Server  │   │   App Server  │   │   App Server  │
└───────┬───────┘   └───────┬───────┘   └───────┬───────┘
        │                   │                   │
        └───────────────────┼───────────────────┘
                            ▼
              ┌─────────────────────────┐
              │   Session Store         │
              │   (Redis/JDBC/Hazelcast)│
              └─────────────────────────┘

Redis Configuration

application.yml

spring:
  data:
    redis:
      host: localhost
      port: 6379
      password: ${REDIS_PASSWORD:}

  session:
    store-type: redis
    timeout: 30m
    redis:
      namespace: spring:session
      flush-mode: on_save  # immediate or on_save
      cleanup-cron: "0 * * * * *"  # Every minute

Java Configuration

@Configuration
@EnableRedisHttpSession(
    maxInactiveIntervalInSeconds = 1800,  // 30 minutes
    redisNamespace = "myapp:session",
    flushMode = FlushMode.ON_SAVE
)
public class SessionConfig {

    @Bean
    public LettuceConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory();
    }

    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
}

JDBC Configuration

application.yml

spring:
  session:
    store-type: jdbc
    timeout: 30m
    jdbc:
      initialize-schema: always  # always, embedded, never
      table-name: SPRING_SESSION
      cleanup-cron: "0 * * * * *"

Schema

-- Auto-created with initialize-schema: always
-- Or create manually:

CREATE TABLE SPRING_SESSION (
    PRIMARY_ID CHAR(36) NOT NULL,
    SESSION_ID CHAR(36) NOT NULL,
    CREATION_TIME BIGINT NOT NULL,
    LAST_ACCESS_TIME BIGINT NOT NULL,
    MAX_INACTIVE_INTERVAL INT NOT NULL,
    EXPIRY_TIME BIGINT NOT NULL,
    PRINCIPAL_NAME VARCHAR(100),
    CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
);

CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);

CREATE TABLE SPRING_SESSION_ATTRIBUTES (
    SESSION_PRIMARY_ID CHAR(36) NOT NULL,
    ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
    ATTRIBUTE_BYTES BYTEA NOT NULL,
    CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
    CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID)
        REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
);

Session Usage

Controller

@RestController
@RequestMapping("/api")
public class SessionController {

    @GetMapping("/session/info")
    public Map<String, Object> getSessionInfo(HttpSession session) {
        return Map.of(
            "sessionId", session.getId(),
            "creationTime", new Date(session.getCreationTime()),
            "lastAccessedTime", new Date(session.getLastAccessedTime()),
            "maxInactiveInterval", session.getMaxInactiveInterval()
        );
    }

    @PostMapping("/session/attribute")
    public void setAttribute(
            HttpSession session,
            @RequestParam String key,
            @RequestParam String value) {
        session.setAttribute(key, value);
    }

    @GetMapping("/session/attribute/{key}")
    public String getAttribute(HttpSession session, @PathVariable String key) {
        return (String) session.getAttribute(key);
    }

    @DeleteMapping("/session/invalidate")
    public void invalidateSession(HttpSession session) {
        session.invalidate();
    }
}

@SessionAttributes

@Controller
@SessionAttributes("cart")
public class CartController {

    @ModelAttribute("cart")
    public Cart createCart() {
        return new Cart();
    }

    @PostMapping("/cart/add")
    public String addToCart(
            @ModelAttribute("cart") Cart cart,
            @RequestParam Long productId) {
        cart.addItem(productId);
        return "redirect:/cart";
    }

    @PostMapping("/cart/checkout")
    public String checkout(
            @ModelAttribute("cart") Cart cart,
            SessionStatus status) {
        orderService.createOrder(cart);
        status.setComplete();  // Remove from session
        return "redirect:/orders";
    }
}

@SessionScope Bean

@Component
@SessionScope
public class UserPreferences implements Serializable {

    private String theme = "light";
    private String language = "en";
    private int pageSize = 20;

    // getters and setters
}

@RestController
public class PreferencesController {

    @Autowired
    private UserPreferences preferences;  // Session-scoped

    @PutMapping("/preferences/theme")
    public void setTheme(@RequestParam String theme) {
        preferences.setTheme(theme);
    }
}

Security Integration

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .maximumSessions(1)  // One session per user
                .maxSessionsPreventsLogin(true)  // Prevent new login
                .sessionRegistry(sessionRegistry())
            )
            .logout(logout -> logout
                .invalidateHttpSession(true)
                .deleteCookies("SESSION")
            );
        return http.build();
    }

    @Bean
    public SpringSessionBackedSessionRegistry<?> sessionRegistry() {
        return new SpringSessionBackedSessionRegistry<>(sessionRepository);
    }
}

Find Sessions by Principal

@Service
@RequiredArgsConstructor
public class SessionManagementService {

    private final FindByIndexNameSessionRepository<?> sessionRepository;

    public Map<String, ?> findSessionsByUsername(String username) {
        return sessionRepository.findByPrincipalName(username);
    }

    public void invalidateUserSessions(String username) {
        Map<String, ?> sessions = sessionRepository.findByPrincipalName(username);
        sessions.keySet().forEach(sessionRepository::deleteById);
    }
}

Custom Session Repository

@Configuration
@EnableRedisHttpSession
public class CustomSessionConfig {

    @Bean
    public SessionRepositoryCustomizer<RedisIndexedSessionRepository> customizer() {
        return sessionRepository -> {
            sessionRepository.setDefaultMaxInactiveInterval(Duration.ofMinutes(30));
            sessionRepository.setRedisKeyNamespace("app:sessions");
        };
    }
}

Session Events

@Component
@Slf4j
public class SessionEventListener {

    @EventListener
    public void onSessionCreated(SessionCreatedEvent event) {
        log.info("Session created: {}", event.getSessionId());
    }

    @EventListener
    public void onSessionDeleted(SessionDeletedEvent event) {
        log.info("Session deleted: {}", event.getSessionId());
    }

    @EventListener
    public void onSessionExpired(SessionExpiredEvent event) {
        log.info("Session expired: {}", event.getSessionId());
    }
}

Cookie Configuration

@Configuration
public class CookieConfig {

    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setCookieName("SESSIONID");
        serializer.setCookiePath("/");
        serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
        serializer.setSameSite("Strict");
        serializer.setUseSecureCookie(true);
        serializer.setUseHttpOnlyCookie(true);
        serializer.setCookieMaxAge(3600);  // 1 hour
        return serializer;
    }
}

Header-Based Session (REST APIs)

@Configuration
@EnableRedisHttpSession
public class HeaderSessionConfig {

    @Bean
    public HttpSessionIdResolver httpSessionIdResolver() {
        return HeaderHttpSessionIdResolver.xAuthToken();
        // Or custom header:
        // return new HeaderHttpSessionIdResolver("X-Session-Id");
    }
}

// Client sends: X-Auth-Token: <session-id>

WebSocket Session

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new SessionChannelInterceptor());
    }
}

public class SessionChannelInterceptor implements ChannelInterceptor {

    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            // Access session attributes
            Map<String, Object> sessionAttributes = accessor.getSessionAttributes();
        }
        return message;
    }
}

Testing

@SpringBootTest
@AutoConfigureMockMvc
class SessionControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldMaintainSessionAcrossRequests() throws Exception {
        // First request - set attribute
        MvcResult result = mockMvc.perform(post("/api/session/attribute")
                .param("key", "user")
                .param("value", "john"))
            .andExpect(status().isOk())
            .andReturn();

        MockHttpSession session = (MockHttpSession) result.getRequest().getSession();

        // Second request - get attribute with same session
        mockMvc.perform(get("/api/session/attribute/user")
                .session(session))
            .andExpect(status().isOk())
            .andExpect(content().string("john"));
    }
}

Best Practices

DoDon't
Use Redis for distributed sessionsUse in-memory sessions in cluster
Configure proper timeoutInfinite session timeout
Serialize session data properlyStore non-serializable objects
Clean up expired sessionsLet sessions accumulate
Secure session cookiesUse insecure cookies

Production Checklist

  • Distributed store configured (Redis/JDBC)
  • Session timeout set appropriately
  • Cookie security (Secure, HttpOnly, SameSite)
  • Cleanup cron configured
  • Maximum sessions per user
  • Session fixation protection
  • Serialization configured (JSON)
  • Monitoring session count
  • Redis cluster for HA
  • Session events logging

When NOT to Use This Skill

  • Stateless JWT auth - Use jwt or spring-security skills
  • OAuth2 tokens - Use oauth2 skill
  • Simple single-instance apps - In-memory sessions suffice
  • REST APIs - Consider stateless design

Anti-Patterns

Anti-PatternProblemSolution
In-memory sessions in clusterSession lost on failoverUse Redis/JDBC store
Infinite session timeoutResource exhaustionSet appropriate timeout
Non-serializable objectsSession save failsEnsure Serializable
No cleanup cronSessions accumulateConfigure cleanup
Insecure cookiesSession hijackingUse Secure, HttpOnly

Quick Troubleshooting

ProblemDiagnosticFix
Session not persistedCheck store-typeConfigure Redis/JDBC
Session lost between nodesCheck store connectivityVerify Redis/DB connection
Serialization errorCheck object typesMake objects Serializable
Cookie not sentCheck cookie configEnable Secure for HTTPS
Session expires too fastCheck timeoutIncrease session timeout

Reference Documentation

Signals

GitHub stars
33
Forks
6
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
spring-session
Source
github.com/claude-dev-suite/claude-dev-suite