How do I log trace id in Java Spring Async thread
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
In Spring applications, trace IDs are often stored in a thread-local logging context, so asynchronous execution can lose them unless you propagate context explicitly. This becomes a major debugging problem when @Async methods write logs without the request trace ID. A reliable setup copies diagnostic context from the caller thread into worker threads and clears it after execution.
Why Trace Context Is Lost with @Async
@Async runs methods on a thread pool. Logging frameworks usually keep context in MDC, which is thread-local. When work moves to another thread, the context map is empty unless you copy it.
Without propagation, the log line above may miss traceId, even when the caller thread had one.
Propagate MDC with TaskDecorator
Spring ThreadPoolTaskExecutor supports TaskDecorator, which is a clean way to capture and restore MDC.
This pattern prevents context leaks between tasks and ensures each async task sees the caller context.
Ensure Incoming Requests Populate Trace ID
You still need to initialize MDC at request entry. A servlet filter is a common place.
Pair this with a logging pattern that prints %X{traceId} so every log line includes it when present.
If You Use Micrometer Tracing
In newer Spring Boot stacks, Micrometer Tracing can manage trace propagation across supported async boundaries. Even then, custom executors can still need explicit configuration, especially when third-party thread pools are involved. Keep one integration test that verifies trace ID appears in logs from an @Async method, so upgrades do not silently break observability.
Add a Quick Verification Endpoint
A lightweight endpoint helps you validate propagation in local and staging environments. Trigger a request with a known trace header, then inspect both controller and async logs for the same value.
This manual check catches configuration drift quickly, especially after executor, logging, or dependency changes.
Common Pitfalls
- Adding
@Asyncbut using the default executor without context propagation. - Copying MDC into worker threads and forgetting to clear it, which can leak IDs across unrelated requests.
- Setting trace ID in business code instead of at request boundaries, causing inconsistent logs.
- Assuming all async libraries propagate context automatically. Many do not unless explicitly configured.
- Omitting log pattern fields such as
%X{traceId}, making propagation look broken even when context exists.
Summary
@Asyncswitches threads, so MDC trace data is not preserved automatically.- Use
TaskDecoratoron yourThreadPoolTaskExecutorto copy and clear MDC safely. - Initialize
traceIdat request entry, then reuse it across logs and response headers. - Keep log patterns and tests aligned so trace fields are always visible.
- Re-check propagation after framework upgrades and executor changes.
Related reading
- How do I make cloud-init startup scripts run every time my EC2 instance boots?
- How do I monitor clipboard content changes in C?
- How do I monitor the computer''s CPU, memory, and disk usage in Java?
- How do I pass environment variables to Docker containers?
- How do I make a large number of concurrent HTTPS requests robustly in Clojure /Java
- How do I make multiple async calls, then execute a function after all calls have completed?
- How do I make a delay in Java?
- How do I make my ArrayList Thread-Safe? Another approach to problem in Java?

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.