Class FlowContextPropagatingExecutor
- All Implemented Interfaces:
Executor
Executor decorator that carries the submitting thread's
FlowContext and MDC context over to whatever
thread actually runs the submitted task.
FlowContextHolder tracks call-chain position through a
ThreadLocal, which by definition only exists on the thread that
set it. As soon as work is handed off to a different thread — a
@Async method, a CompletableFuture
running on a custom Executor, a task submitted to
Executors.newVirtualThreadPerTaskExecutor() —
that thread starts with no context of its own, and any @Logged
call made from within it is reported as the start of a brand new chain
instead of a continuation of the caller's chain. This class closes that
gap for any Executor-based hand-off, including virtual thread
executors: an Executor is an Executor regardless of the
kind of thread backing it, so no separate handling is needed for virtual
threads specifically.
Wrapping is a one-time setup step; call sites do not change:
Executor virtualThreadExecutor =
Executors.newVirtualThreadPerTaskExecutor();
Executor propagating =
new FlowContextPropagatingExecutor(virtualThreadExecutor);
// Inside a @Logged method:
propagating.execute(() -> {
// Any @Logged call made here is recognized as part of the
// caller's chain, at the same depth the caller was at.
someOtherLoggedBean.doWork();
});
The context is captured once per execute(Runnable) call, on
the calling thread, at the moment the task is submitted — not when it
starts running. This matters because a pooled Executor may not
run the task immediately; capturing eagerly guarantees the propagated
context reflects the caller's state at submission time, which is what a
human reading the resulting call chain would expect, rather than whatever
happened to be active on the calling thread later when the pool got
around to it.
This class is deliberately not a Spring bean and requires no
ApplicationContext: it only depends on FlowContextHolder
and the Executor it wraps, so it can decorate any executor a
consuming application already manages, including ones created outside of
Spring's control.
-
Constructor Summary
ConstructorsConstructorDescriptionFlowContextPropagatingExecutor(Executor delegate) Wrapsdelegateso that every task submitted through this executor carries the submitting thread'sFlowContextinto whichever threaddelegateactually runs it on. -
Method Summary
Modifier and TypeMethodDescriptionvoidCaptures the calling thread's currentFlowContextand MDC context map, and submits a wrapped task to the delegate executor that restores both before runningcommandand restores the executor thread's own previous state again afterward, regardless of whethercommandcompletes normally or throws.
-
Constructor Details
-
FlowContextPropagatingExecutor
Wrapsdelegateso that every task submitted through this executor carries the submitting thread'sFlowContextinto whichever threaddelegateactually runs it on.- Parameters:
delegate- the executor that will actually run submitted tasks
-
-
Method Details
-
execute
Captures the calling thread's currentFlowContextand MDC context map, and submits a wrapped task to the delegate executor that restores both before runningcommandand restores the executor thread's own previous state again afterward, regardless of whethercommandcompletes normally or throws.The MDC context map is captured and restored in full, not limited to
LoggedMdcKeys: seeMdcContextPropagationfor why.
-