ReactiveSecurityContextHolder
Spring WebFlux
Security
Reactive Programming
Java Spring

ReactiveSecurityContextHolder is empty in Spring WebFlux

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

When ReactiveSecurityContextHolder is empty in Spring WebFlux, the usual problem is not that Spring Security forgot to authenticate the request. The usual problem is that the code is trying to read the security context outside the reactive chain, or after crossing a boundary where the Reactor context is no longer being used. In WebFlux, security context lives in Reactor Context, not in a servlet ThreadLocal.

Understand Where the Security Context Lives

In servlet applications, security is often read through thread-bound context. In WebFlux, that model does not apply reliably because work may continue on different threads.

The reactive security context travels with the Reactor pipeline. That means code must access it from within the reactive flow:

java
1import org.springframework.security.core.context.ReactiveSecurityContextHolder;
2import reactor.core.publisher.Mono;
3
4public Mono<String> currentUserName() {
5    return ReactiveSecurityContextHolder.getContext()
6        .map(context -> context.getAuthentication().getName());
7}

If you call ReactiveSecurityContextHolder.getContext() outside a subscribed reactive path, you should expect it to be empty.

Typical Controller Usage

Inside WebFlux handlers or services that stay reactive, reading the context is straightforward:

java
1@GetMapping("/me")
2public Mono<String> me() {
3    return ReactiveSecurityContextHolder.getContext()
4        .map(ctx -> ctx.getAuthentication().getName());
5}

This works because the request is already flowing through the Spring Security WebFlux filter chain, which populates the Reactor context for the request pipeline.

Why It Becomes Empty

The security context often appears empty for one of these reasons:

  • the code called .block() and left the reactive pipeline
  • the code started an unrelated reactive sequence later
  • authentication was never established by the filter chain
  • the logic is running in a test or background task without context setup

A common anti-pattern looks like this:

java
Mono<SecurityContext> context = ReactiveSecurityContextHolder.getContext();
// later, elsewhere, outside the request flow
context.subscribe(System.out::println);

The later subscription may not happen in the original request context you expected.

Prefer Passing Authentication Through the Chain

Often the cleanest fix is not to re-read from the holder deep in the code. Instead, extract the principal or authentication near the entry point and pass it through the reactive chain.

java
1public Mono<String> loadProfile() {
2    return ReactiveSecurityContextHolder.getContext()
3        .flatMap(ctx -> {
4            String username = ctx.getAuthentication().getName();
5            return userService.findProfile(username);
6        });
7}

This keeps the dependency on security explicit and avoids hidden context assumptions later.

Testing Requires Explicit Setup

In tests, the context is often empty because no authenticated WebFlux request populated it. For reactive tests, use Spring Security test support or manually set up the context expected by the pipeline.

If the test just subscribes to a Mono directly without WebFlux security integration, there may be no authentication present to read.

Common Pitfalls

The biggest mistake is treating ReactiveSecurityContextHolder like a thread-local API. In WebFlux, the value is tied to Reactor context propagation, not to the current Java thread.

Another issue is breaking the reactive chain with blocking calls or by creating detached asynchronous work. Once you leave the request pipeline, the security context may no longer be present.

People also often debug the wrong layer. If the filter chain never authenticated the request, the holder will naturally be empty, and the real problem is earlier in the security configuration.

Finally, do not overuse the holder in deep application code when the principal could be passed explicitly from higher layers. Explicit data flow is usually easier to reason about.

Summary

  • 'ReactiveSecurityContextHolder reads from Reactor context, not servlet-style thread-local state.'
  • It works only when accessed inside the authenticated reactive request pipeline.
  • Blocking or detached execution often makes the security context appear empty.
  • In tests, you must explicitly provide the security context or use Spring Security test support.
  • Passing authentication through the reactive chain is often cleaner than reading the holder everywhere.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.