Observation context does not follow an arbitrary thread or reactive operator unless the corresponding propagation path is configured.
Spring Boot observation context across @Async and Reactor handoffs
Find the thread switch
An HTTP request starts a trace, then submits parcel scoring to @Async. A new thread does not inherit ThreadLocal observation state by default. For Boot's auto-configured AsyncTaskExecutor, opt into context propagation. For a custom executor, use a ContextPropagatingTaskDecorator. Reactor operators have their own context path; automatic ThreadLocal restoration is controlled separately. Database transactions also do not cross the handoff merely because the trace does.
Limit what travels
Context propagation is for observation state, not a substitute for passing an explicit tenant ID or business correlation key. When a detached task outlives the HTTP request, record its durable job ID and start a meaningful new span if necessary. An in-process task can disappear during restart even if its trace looked complete.
Test two paths
Capture a request trace ID, invoke the async scorer, and assert its span stays attached with propagation enabled. Run the same check through a Reactor chain when that stack is used. Force an executor rejection too: no child span should be mistaken for completed business work. The configuration below targets Boot-managed executors; a custom pool needs its own decorator.
Implementation sketch
spring:
task:
execution:
propagate-context: true
reactor:
context-propagation: autoCost and verification
Capturing and restoring context adds allocation and bookkeeping at each handoff. Measure high-volume task paths and keep baggage small.
Common Mistakes
- Do not assume ThreadLocal trace state appears in a new worker thread.
- Do not use propagated observation context as the tenant authorization source.
- Do not confuse a recorded async span with durable task completion.
Read next
Spring Boot tracing across RestClient: construct the client from Boot's builder, Spring async trace context: follow work across executor threads, Spring imperative transaction: a worker thread does not inherit it, Spring async work versus durable delivery: separate latency from recovery.
