SecurityContext and SecurityContextHolder — How They Work
Fast recall
Quick revision
Core answer
SecurityContext is populated by authentication filters and cleared after the response (in a finally block) to prevent leakage between requests sharing pooled threads. The ThreadLocal model breaks under `@Async`, `CompletableFuture`, and reactive code: the worker thread sees an empty context. Fixes: `MODE_INHERITABLETHREADLOCAL` (only safe for non-pooled child threads), `DelegatingSecurityContextExecutor` (production solution for thread pools), and `ReactiveSecurityContextHolder` + Reactor Context for WebFlux.Deep understanding of the thread-local security model and its implications for async code.
Accessing SecurityContextHolder in a @Async method or new Thread and getting null — the child thread has its own empty ThreadLocal.
Discuss MODE_INHERITABLETHREADLOCAL, DelegatingSecurityContextExecutor, and how reactive (WebFlux) Spring Security uses Reactor Context instead of ThreadLocal.
Interview-ready guide
Interview answer
How it works
- After successful authentication, Spring stores an
Authenticationin theSecurityContextheld bySecurityContextHolder - Service methods call
SecurityContextHolder.getContext().getAuthentication()for current user - At end of request, Spring Security clears the context
The async thread problem
@Asyncruns on a thread pool — different thread from the HTTP requestSecurityContextHolder.getContext().getAuthentication()returns null in@Asyncby default- Fix 1:
MODE_INHERITABLETHREADLOCAL— child threads inherit; does NOT work for pools - Fix 2:
DelegatingSecurityContextExecutorwraps the executor; each task gets a copy of parent's context — production solution
WebFlux
- No single thread per request — ThreadLocal would always be empty
ReactiveSecurityContextHolderstores in Reactor Context (attached to reactive pipeline)ReactorContextWebFilterpopulates it from session or JWT
Cleanup matters
SecurityContextHolderFilterclears in finally block- Without clear: thread returning to pool carries previous user's auth -> privilege escalation
- Manual context manipulation must clear in finally
Persistence
- Session-based:
HttpSessionSecurityContextRepositorysaves to session before response - Stateless JWT: no persistence — JWT IS the identity
Modern considerations
- Project Loom virtual threads:
ThreadLocalworks; considerScopedValuefor pinning - Test with
@WithMockUser— doesn't polluteSecurityContextHolderacross tests
SecurityContext and SecurityContextHolder — How They Work
Direct Answer
SecurityContextHolder is a static class that stores the current SecurityContext using a ThreadLocal by default, making the authenticated user's Authentication accessible anywhere on the request thread without parameter passing. The SecurityContext is populated by authentication filters and cleared after the response (in a finally block) to prevent leakage between requests sharing pooled threads. The ThreadLocal model breaks under @Async, CompletableFuture, and reactive code: the worker thread sees an empty context. Fixes: MODE_INHERITABLETHREADLOCAL (only safe for non-pooled child threads), DelegatingSecurityContextExecutor (production solution for thread pools), and ReactiveSecurityContextHolder + Reactor Context for WebFlux.
Common Mistakes & Pitfalls
Accessing SecurityContextHolder in a @Async method or new Thread and getting null — the child thread has its own empty ThreadLocal.
Check your understanding
Answer these without looking back at the article.
- How does Spring Security store and restore the SecurityContext across requests for session-based authentication?
- What changes in the SecurityContext handling model between Spring Security 5 and Spring Security 6?
- How do you propagate the SecurityContext to virtual threads (Project Loom) in Spring Boot 3?
- Why does @Async lose authentication and how does DelegatingSecurityContextExecutor fix it?
Related interview prep categories
2Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.