We hit a subtle bug after migrating to Axon 5 with Spring WebFlux and Netflix DGS: ReactiveSecurityContextHolder.getContext() always returns Mono.empty() inside @QueryHandler methods.
Owners couldn’t see their own INACTIVE entities because the auth check silently fell through.
Root cause: Two things break Reactor Context propagation:
- subscribeOn() shifting to a different thread pool (which turns out to be unnecessary in Axon 5 — SimpleQueryBus is fully non-blocking)
- Axon’s internal .toFuture() call creating an independent subscription with no knowledge of the original context
Our solution: Extract identity and roles at the edge (DGS layer, where security context still works) and attach them as Axon MetaData via GenericQueryMessage. Query handlers read them
with @MetadataValue:
@QueryHandler
public Mono<Venue> handle(FindVenueByIdQuery query,
@MetadataValue(value = "userId", required = false) String callerId,
@MetadataValue(value = "roles", required = false) String roles) {
// pure function — no ReactiveSecurityContextHolder needed
}
The key API insight: QueryGateway doesn’t expose a metadata parameter directly, but if you pass a GenericQueryMessage, Axon uses it as-is — metadata included.
Full write-up with implementation details, Mermaid diagram, and Axon 5 migration notes: Propagating User Identity in Axon 5 Query Handlers — SaaSForge