Three verified programs (JDK 25, Spring Security 7.1.1, Spring Boot 4.1.1 dependency versions) backing the ankurm.com post: InheritableThreadLocal across thread models, @Async on a virtual-thread SimpleAsyncTaskExecutor (DelegatingSecurityContextExecutor vs ContextPropagatingTaskDecorator), and StructuredTaskScope.fork() propagation. Captured console output and docs chapters included.
2.8 KiB
spring-security-demo
Companion repo for Spring Security Context Propagation - Complete
Guide on ankurm.com.
Three small, dependency-light programs that answer one question each: does a Spring Security
SecurityContext survive a specific thread hand-off? No web server, no HTTP -- every scenario
sets an Authentication on the calling thread and checks whether the other side of the hand-off
can see it.
Verified versions
| Component | Version |
|---|---|
| JDK | 25 (Temurin 25.0.4.1+1), LTS, GA 2025-09-16 |
| Spring Boot (reference target) | 4.1.1 |
| Spring Framework | 7.0.9 |
| Spring Security | 7.1.1 |
io.micrometer:context-propagation |
1.2.1 (as managed by Boot 4.1.1's BOM) |
StructuredTaskScope is a preview API on JDK 25 (JEP 505, fifth preview) and remains
preview through JDK 26 (JEP 525, sixth preview) -- every build/run command below needs
--enable-preview.
Quickstart
mvn dependency:build-classpath -Dmdep.outputFile=cp.txt
javac --release 25 --enable-preview -cp "$(cat cp.txt)" -d target/classes $(find src -name '*.java')
java --enable-preview -cp "target/classes:$(cat cp.txt)" com.ankurm.vt.Demo1PlainThreadLocal
Or just run everything and regenerate the captured output: scripts/run-all.sh.
What each demo shows
| Demo | Question | Chapter |
|---|---|---|
Demo1PlainThreadLocal |
Does InheritableThreadLocal behave differently for a pooled platform thread vs. a fresh virtual thread? (No Spring.) |
docs/01 |
Demo2AsyncVirtualThreads |
Does the Boot-4.1-style virtual-thread @Async executor propagate SecurityContext, and what four fixes change? |
docs/02 |
Demo3StructuredConcurrency |
Does a StructuredTaskScope.fork() subtask see the parent's SecurityContext? |
docs/03 |
Captured output
Every number and log line in the blog post traces back to one of these, produced by
scripts/run-all.sh, not retyped:
The one-line summary of all three chapters
SecurityContextHolder is a ThreadLocal. Nothing about virtual threads or structured
concurrency changes that. What changed is that virtual threads are never pooled, so
MODE_INHERITABLETHREADLOCAL's old danger (stale context on a reused pool worker) doesn't
apply to them -- and Spring Security 6.5 gave SecurityContextHolder a Micrometer
ThreadLocalAccessor, so ContextPropagatingTaskDecorator / ContextSnapshot.wrap(...) now
propagate it automatically, alongside MDC and tracing context, without a
DelegatingSecurityContext* wrapper.