How to Propagate SLF4J MDC Request IDs Across Async Threads in Java
This article explains how to use SLF4J's MDC to tag logs with a request ID, why the ID is lost in asynchronous threads, and how to apply a decorator pattern (MDCRunnable) so that the request ID is correctly propagated and searchable across all threads and thread pools.
Introduction
When a request fails in production we often need to collect every log entry belonging to that request. If processing stays in a single thread we can filter by thread ID, but asynchronous processing makes this approach ineffective.
The Huawei IoT platform receives device data and processes it asynchronously (storage, rule engine, push, command issuance, etc.). The key problem is how to quickly filter all logs of a single data‑report request.
Basic Usage of MDC
SLF4J provides an MDC (Mapped Diagnostic Context) utility. By putting a unique requestId into MDC at the start of a request and removing it at the end, the ID appears in every log line, allowing a simple grep requestId=xxx *.log to retrieve the whole trace.
public class Main {
private static final String KEY = "requestId";
private static final Logger logger = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
// entry: put requestId
MDC.put(KEY, UUID.randomUUID().toString());
// print logs
logger.debug("log in main thread 1");
logger.debug("log in main thread 2");
logger.debug("log in main thread 3");
// exit: remove requestId
MDC.remove(KEY);
}
}The console output shows the requestId inside curly braces for each log entry.
Advanced: MDC in Asynchronous Threads
Because MDC stores data in a ThreadLocal, the context does not automatically propagate to new threads. To make MDC work in async scenarios we can create a decorator class MDCRunnable that captures the current MDC map and restores it inside the new thread before executing the original Runnable.
public class MDCRunnable implements Runnable {
private final Runnable runnable;
private final Map<String, String> contextMap;
public MDCRunnable(Runnable runnable) {
this.runnable = runnable;
// capture current thread's MDC values
this.contextMap = MDC.getCopyOfContextMap();
}
@Override
public void run() {
// inject captured MDC values into this thread
if (contextMap != null) {
for (Map.Entry<String, String> entry : contextMap.entrySet()) {
MDC.put(entry.getKey(), entry.getValue());
}
}
// execute original logic
runnable.run();
// clean up
if (contextMap != null) {
for (String key : contextMap.keySet()) {
MDC.remove(key);
}
}
}
}When creating a new thread or submitting a task to an executor, wrap the original Runnable with new MDCRunnable(...). The requestId is now present in logs from both the main thread and asynchronous workers.
public class Main {
private static final String KEY = "requestId";
private static final Logger logger = LoggerFactory.getLogger(Main.class);
private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();
public static void main(String[] args) {
MDC.put(KEY, UUID.randomUUID().toString());
logger.debug("log in main thread");
// async thread using MDCRunnable
new Thread(new MDCRunnable(() -> {
logger.debug("log in other thread");
})).start();
// thread‑pool task using MDCRunnable
EXECUTOR.execute(new MDCRunnable(() -> {
logger.debug("log in other thread pool");
}));
EXECUTOR.shutdown();
MDC.remove(KEY);
}
}Running the program produces log lines where the same requestId appears in the main thread, the manually created thread, and the thread‑pool worker.
Conclusion
The article demonstrates how to use SLF4J MDC to tag logs with a request identifier, how to propagate that identifier to asynchronous threads via a decorator pattern, and why this greatly speeds up debugging in development and operations environments.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
