Repository navigation
Description
Describe the feature
Allow the future completion executor of async clients to know which request it is completing. Concretely: add an optional executor that receives the request's ExecutionAttributes along with the Runnable to execute.
Combined with an ExecutionInterceptor, which already runs on the caller's thread in beforeExecution, this would let users capture any caller context when the call starts and restore it when the future completes. That covers MDC, the Spring Security context, the OpenTelemetry Context, the thread context class loader, or the Vert.x context. The change is additive and non-breaking.
This is a concrete proposal for the problem reported in #5242 (and earlier in #2142 and #1613).
Use Case
Async clients complete their futures on SDK threads: the FUTURE_COMPLETION_EXECUTOR, or the Netty/CRT event loop. Any context bound to the caller's thread is lost in the continuation, and there is currently no SDK extension point to restore it:
FUTURE_COMPLETION_EXECUTORis configured once per client and receives a bareRunnableon a Netty thread. It cannot know which request, and therefore which caller context, it is completing. As explained in MDC context is lost when calling asynchronous operations with executor #2142, it "only offloads" the completion.ExecutionInterceptor.beforeExecutionruns on the caller's thread and can capture the context into anExecutionAttribute, but it cannot influence where the future is completed.
My use case is the Quarkus extension for the AWS SDK. In Quarkus, reactive code (Hibernate Reactive, Mutiny, reactive REST endpoints) runs on a Vert.x event loop and relies on the Vert.x duplicated context bound to that thread. When an async client future completes on an SDK thread, the continuation loses that context, and Hibernate Reactive fails with No current Vertx context found (quarkiverse/quarkus-amazon-services#2145, #998). Today users have to switch back by hand after every call:
Context ctx = Vertx.currentContext();
return Uni.createFrom().completionStage(() -> client.getItem(request))
.emitOn(cmd -> ctx.runOnContext(v -> cmd.run()));I started making the extension handle this natively, but doing it from outside the SDK is getting complex. The only workaround we found (in progress, not released) wraps each async client bean in a proxy that captures the context and re-completes every returned future on it. It requires native-image proxy configuration and per-service-client special cases (for example, S3TransferManager checks instanceof S3CrtAsyncClient). It also does nothing for clients that users build themselves. With the proposal below, all of it would be replaced by one interceptor and one executor.
Proposed Solution
These are additive changes only. FUTURE_COMPLETION_EXECUTOR and its Executor type stay as they are.
@SdkPublicApi
@FunctionalInterface
public interface RequestAwareExecutor {
void execute(Runnable command, ExecutionAttributes executionAttributes);
}
// SdkAdvancedAsyncClientOption
public static final SdkAdvancedAsyncClientOption<RequestAwareExecutor> REQUEST_AWARE_FUTURE_COMPLETION_EXECUTOR = ...;- Client builder resolution: internally, the SDK always uses a
RequestAwareExecutor.- If only
FUTURE_COMPLETION_EXECUTORis set, or neither option is set (default executor), the builder adapts it as(cmd, attrs) -> executor.execute(cmd). Behavior is unchanged. - If
REQUEST_AWARE_FUTURE_COMPLETION_EXECUTORis set, it takes precedence. Rejecting a configuration that sets both would also be fine.
- If only
- Call sites: as far as I can see (2.55.10), the completion executor is used in two places.
MakeAsyncHttpRequestStage.executeHttpRequestcallsresponseHandlerFuture.handleAsync(..., futureCompletionExecutor). TheRequestExecutionContext, and soexecutionAttributes(), is already in scope there. The change would becomehandleAsync(..., cmd -> requestAwareExecutor.execute(cmd, context.executionAttributes())).- Generated event-stream operations (e.g.
DefaultS3AsyncClient.selectObjectContent) pass it toEventStreamAsyncResponseTransformer.builder().executor(...). They could pass anExecutorbound to the request's attributes the same way.
- S3 CRT client: add a
S3CrtAsyncClientBuilder.futureCompletionExecutor(RequestAwareExecutor)overload next to the existing one.
With this in place, context propagation needs only one interceptor and one executor, both built on public SDK APIs. Example with MDC:
static final ExecutionAttribute<Map<String, String>> MDC = new ExecutionAttribute<>("MDC");
// interceptor: runs on the caller's thread
public void beforeExecution(Context.BeforeExecution ctx, ExecutionAttributes attrs) {
attrs.putAttribute(MDC, org.slf4j.MDC.getCopyOfContextMap());
}
// completion executor: restores what the interceptor captured
RequestAwareExecutor executor = (cmd, attrs) -> pool.execute(() -> {
Map<String, String> mdc = attrs.getAttribute(MDC);
if (mdc != null) {
org.slf4j.MDC.setContextMap(mdc);
}
try {
cmd.run();
} finally {
org.slf4j.MDC.clear();
}
});The same pattern covers the Spring Security context, servlet request attributes, the OpenTelemetry Context, the thread context class loader, or, for Quarkus, vertxContext.runOnContext(v -> cmd.run()). Because it relies on the request pipeline, it also applies to waiters, paginators and S3 Transfer Manager sub-requests, with no per-client wrapping.
Other Information
Related issues:
- Thread locals are lost after the control is returned from SDK methods to the client code #5242: Thread locals are lost after the control is returned from SDK methods to the client code (open)
- MDC context is lost when calling asynchronous operations with executor #2142: MDC context is lost when calling asynchronous operations with executor (closed for staleness)
- MDC Context coming as null #1613: MDC Context coming as null (closed)
- Discussion Propagation a local thread variable from an user of `SqsAsyncClient` #5363: Propagation a local thread variable from an user of
SqsAsyncClient(unanswered) - Add support for Micrometer's Observation API #4611: Micrometer Observation API support. This is related, as the team mentioned a pluggable client-level setting for observability tools.
Acknowledgements
- I may be able to implement this feature request
- This feature might incur a breaking change
AWS Java SDK version used
2.55.10
JDK version used
openjdk version "21.0.11" 2026-04-21 LTS
Operating System and version
Linux (Fedora 44, kernel 7.2.9)
Activity
scrocquesel commentedon Oct 31, 2026
This is only part of the issue, I discovered that api call timeout will not guarantee to use the FUTURE_COMPLETION_EXECUTOR. I don't know if there is a reason behind this choice.