Backend Development 13 min read

Spring Boot Session Auto‑Configuration: How Filters and Repositories Work

This article walks through Spring Boot 2.3's session auto‑configuration process, detailing how core SessionConfiguration classes are registered, how the appropriate SessionRepository is selected based on store type, and how the SessionRepositoryFilter wraps requests and responses to manage session data with implementations such as Redis.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Boot Session Auto‑Configuration: How Filters and Repositories Work

1 Auto‑Configuration

Spring Boot 2.3.12.RELEASE provides automatic configuration for HTTP session handling. The main entry point is SessionAutoConfiguration , which imports the core filter configuration and registers the appropriate SessionRepository implementation based on the selected store type.

<code>public class SessionAutoConfiguration {
    // SessionRepositoryFilterConfiguration configures the core filter
    // 3 core filters
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @Import({ ServletSessionRepositoryValidator.class, SessionRepositoryFilterConfiguration.class })
    static class ServletSessionConfiguration {
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnMissingBean(SessionRepository.class)
        @Import({ ServletSessionRepositoryImplementationValidator.class,
                 ServletSessionConfigurationImportSelector.class })
        static class ServletSessionRepositoryConfiguration {
        }
    }
    // This class registers all *SessionConfiguration classes such as RedisSessionConfiguration, JdbcSessionConfiguration, etc.
    static class ServletSessionConfigurationImportSelector extends SessionConfigurationImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return super.selectImports(WebApplicationType.SERVLET);
        }
    }
}
</code>

2 Core Session Configuration Objects

The framework registers a set of *SessionConfiguration classes (Redis, Mongo, JDBC, Hazelcast, NoOp) via SessionStoreMappings . These mappings are stored in an immutable map.

<code>final class SessionStoreMappings {
    private static final Map<StoreType, Configurations> MAPPINGS;
    static {
        Map<StoreType, Configurations> mappings = new EnumMap<>(StoreType.class);
        mappings.put(StoreType.REDIS, new Configurations(RedisSessionConfiguration.class, RedisReactiveSessionConfiguration.class));
        mappings.put(StoreType.MONGODB, new Configurations(MongoSessionConfiguration.class, MongoReactiveSessionConfiguration.class));
        mappings.put(StoreType.JDBC, new Configurations(JdbcSessionConfiguration.class, null));
        mappings.put(StoreType.HAZELCAST, new Configurations(HazelcastSessionConfiguration.class, null));
        mappings.put(StoreType.NONE, new Configurations(NoOpSessionConfiguration.class, NoOpReactiveSessionConfiguration.class));
        MAPPINGS = Collections.unmodifiableMap(mappings);
    }
}
</code>

2.1 Register Session Configuration Classes

During import selection, SessionConfigurationImportSelector#selectImports iterates over all registered *SessionConfiguration classes and returns their fully qualified names.

<code>abstract static class SessionConfigurationImportSelector implements ImportSelector {
    protected final String[] selectImports(WebApplicationType webApplicationType) {
        // iterate over all StoreType values and map to configuration class names
        return Arrays.stream(StoreType.values())
            .map(type -> SessionStoreMappings.getConfigurationClass(webApplicationType, type))
            .toArray(String[]::new);
    }
}
</code>

2.2 Register Session Store Object

Each concrete *SessionConfiguration defines a @Bean that creates a SessionRepository implementation. For Redis, SpringBootRedisHttpSessionConfiguration extends RedisHttpSessionConfiguration , which registers a RedisIndexedSessionRepository bean.

<code>@Configuration(proxyBeanMethods = false)
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration {
    @Bean
    public RedisIndexedSessionRepository sessionRepository() {
        // ... create and configure repository ...
    }
}
</code>

3 Core Filter

3.1 Filter Registration

The SessionRepositoryFilterConfiguration registers SessionRepositoryFilter as a servlet filter.

<code>class SessionRepositoryFilterConfiguration {
    @Bean
    FilterRegistrationBean<SessionRepositoryFilter<?>> sessionRepositoryFilterRegistration(
            SessionProperties sessionProperties, SessionRepositoryFilter<?> filter) {
        FilterRegistrationBean<SessionRepositoryFilter<?>> registration = new FilterRegistrationBean<>(filter);
        registration.setDispatcherTypes(getDispatcherTypes(sessionProperties));
        registration.setOrder(sessionProperties.getServlet().getFilterOrder());
        return registration;
    }
}
</code>

3.2 Core Filter Methods

The filter wraps the incoming HttpServletRequest and HttpServletResponse with custom wrappers, delegates to the filter chain, and finally commits the session.

<code>@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
        SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response);
        SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest, response);
        try {
            filterChain.doFilter(wrappedRequest, wrappedResponse);
        } finally {
            wrappedRequest.commitSession();
        }
    }
}
</code>

The SessionRepositoryRequestWrapper provides commitSession() , which saves the session via the configured SessionRepository (e.g., Redis or JDBC).

<code>private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
    private void commitSession() {
        HttpSessionWrapper wrappedSession = getCurrentSession();
        if (wrappedSession == null) {
            if (isInvalidateClientSession()) {
                SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this, this.response);
            }
        } else {
            S session = wrappedSession.getSession();
            clearRequestedSessionCache();
            SessionRepositoryFilter.this.sessionRepository.save(session);
        }
    }
}
</code>

The request wrapper also overrides getSession() to create a new HttpSessionWrapper when needed, delegating attribute handling to the underlying Session implementation.

<code>public class HttpSessionAdapter<S extends Session> implements HttpSession {
    private S session;
    public Object getAttribute(String name) {
        return this.session.getAttribute(name);
    }
    public void setAttribute(String name, Object value) {
        Object oldValue = this.session.getAttribute(name);
        this.session.setAttribute(name, value);
        if (value != oldValue) {
            if (oldValue instanceof HttpSessionBindingListener) {
                ((HttpSessionBindingListener) oldValue).valueUnbound(new HttpSessionBindingEvent(this, name, oldValue));
            }
            if (value instanceof HttpSessionBindingListener) {
                ((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(this, name, value));
            }
        }
    }
}
</code>

3.3 Session Data Storage

When the session is saved, RedisIndexedSessionRepository persists the delta map to Redis and updates index keys for principal names.

<code>public class RedisIndexedSessionRepository implements FindByIndexNameSessionRepository<RedisIndexedSessionRepository.RedisSession>, MessageListener {
    public void save(RedisSession session) {
        session.save();
        if (session.isNew) {
            String sessionCreatedKey = getSessionCreatedChannel(session.getId());
            this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
            session.isNew = false;
        }
    }
    static final class RedisSession implements Session {
        private Map<String, Object> delta = new HashMap<>();
        private void save() {
            saveChangeSessionId();
            saveDelta();
        }
        private void saveDelta() {
            if (this.delta.isEmpty()) return;
            String sessionId = getId();
            getSessionBoundHashOperations(sessionId).putAll(this.delta);
            // handle principal index updates ...
            this.delta = new HashMap<>(this.delta.size());
            Long originalExpiration = (this.originalLastAccessTime != null)
                ? this.originalLastAccessTime.plus(getMaxInactiveInterval()).toEpochMilli() : null;
            this.expirationPolicy.onExpirationUpdated(originalExpiration, this);
        }
    }
}
</code>

In summary, the filter chain processes the request with custom wrappers, the session data is stored in the chosen backend (Redis in this example), and the filter ensures that changes are committed after the request completes.

JavaRedisSpring BootSession ManagementAuto‑Configuration
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.