Every @Async behaviour that surprises people, asserted by a test and captured to docs/output/: the self-invocation trap, what CGLIB cannot override, the IllegalArgumentException a plain return type throws, the unbounded queue that makes max-size decoration, spring.task.execution.propagate-context (new in Boot 4.1.0), the two Executor beans that leave @Async on an unpooled SimpleAsyncTaskExecutor, and JEP 491 measured on JDK 21 against JDK 25.
2.7 KiB
prev: Pool sizing · README · next: Which executor runs it
6. spring.task.execution.propagate-context — new in Boot 4.1
Handing a Callable to another thread leaves every ThreadLocal behind. That is why the async
thread in a stock application sees no MDC entries, no RequestAttributes, and no
SecurityContext — and why "the trace id disappears in the async part" is such a common report.
Spring Boot 4.1.0 added a property for it:
spring:
task:
execution:
propagate-context: true
It decorates the auto-configured executor with ContextPropagatingTaskDecorator, which takes a
snapshot of everything registered with micrometer's ContextRegistry at submission time and
restores it around the task on the executing thread.
How new is it, exactly
New in 4.1.0. It is absent from TaskExecutionProperties in 4.0.8 and in 3.5.9 — checked by
extracting TaskExecutionProperties.class from each spring-boot-autoconfigure jar and running
javap over it, rather than by reading release notes. The accessor pair
getPropagateContext()/setPropagateContext(boolean) appears first in the 4.1.0 jar.
Note also what it is not: there is no matching spring.task.scheduling.propagate-context. The
scheduler is not covered.
Measured
RequestId in this module is a ThreadLocal<String> with a ThreadLocalAccessor registered
against ContextRegistry. Two tests differ only in the property:
ContextNotPropagatedTest— the async thread readsnull.ContextPropagatedTest— the async thread readsreq-4711(output).
The three things that make it silently do nothing
- micrometer's
context-propagationis not on the classpath. The property is still bound and still accepted; nothing decorates the executor. This module declaresio.micrometer:context-propagationexplicitly for that reason. - Nothing registered an accessor. The snapshot only carries what
ContextRegistryknows about. Libraries that ship accessors (Micrometer tracing, Reactor) register their own; aThreadLocalof your own does not register itself. - The executor is not the auto-configured one. The decorator is applied by Boot's
auto-configuration. Declare your own
Executorbean (chapter 7) and you have opted out of the property along with everything else Boot was doing.
SecurityContext propagation is a related but separate mechanism, with its own set of ways to
get it wrong; that is covered in
Virtual Threads and SecurityContext Propagation.
next: Which executor runs it