Using MDC for TraceId Propagation in Java Applications
This article explains what MDC (Mapped Diagnostic Context) is, shows its API and advantages, demonstrates how to add interceptors and log patterns to include a traceId, and provides solutions for traceId loss in child threads and HTTP calls by wrapping thread pools and implementing HTTP client interceptors.
Through this article you will learn what MDC (Mapped Diagnostic Context) is, the problems that can arise when using MDC, and how to solve those problems.
MDC Introduction
MDC is a feature provided by log4j, logback, and log4j2 that allows you to store key‑value pairs in a hash table bound to the current thread. The stored data can be accessed by any code executing in the same thread, and child threads inherit the parent thread's MDC content.
API
clear() – removes all entries from MDC
get(String key) – retrieves the value for the given key
getContext() – returns the entire MDC map
put(String key, Object o) – stores a key‑value pair
remove(String key) – deletes the entry for the given key
Advantages
The code stays concise and log format is unified; you no longer need to manually concatenate the traceId in each log statement, e.g. LOGGER.info("traceId:{} ", traceId) .
MDC Usage – Adding an Interceptor
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Get traceId from request header if present
String traceId = request.getHeader(Constants.TRACE_ID);
if (traceId == null) {
traceId = TraceIdUtil.getTraceId();
}
MDC.put(Constants.TRACE_ID, traceId);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
MDC.remove(Constants.TRACE_ID);
}
}Modify the log pattern to include the traceId:
<property name="pattern">[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>Problems with MDC
TraceId is lost in child threads
TraceId is lost in HTTP calls
Solution for Child‑Thread TraceId Loss – ThreadPool Wrapper
public class ThreadPoolExecutorMdcWrapper extends ThreadPoolExecutor {
public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue
workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
// other constructors omitted for brevity
@Override
public void execute(Runnable task) {
super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public
Future
submit(Runnable task, T result) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()), result);
}
@Override
public
Future
submit(Callable
task) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public Future
submit(Runnable task) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
}Utility class for wrapping tasks:
public class ThreadMdcUtil {
public static void setTraceIdIfAbsent() {
if (MDC.get(Constants.TRACE_ID) == null) {
MDC.put(Constants.TRACE_ID, TraceIdUtil.getTraceId());
}
}
public static
Callable
wrap(final Callable
callable, final Map
context) {
return () -> {
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try {
return callable.call();
} finally {
MDC.clear();
}
};
}
public static Runnable wrap(final Runnable runnable, final Map
context) {
return () -> {
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try {
runnable.run();
} finally {
MDC.clear();
}
};
}
}The wrapper copies the parent thread’s MDC map to the child thread, ensures a traceId exists, executes the original task, and finally clears MDC.
Solution for HTTP Call TraceId Loss
Implement interceptors for the most common HTTP clients and add the traceId to the request header.
HttpClient interceptor :
public class HttpClientTraceIdInterceptor implements HttpRequestInterceptor {
@Override
public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
String traceId = MDC.get(Constants.TRACE_ID);
if (traceId != null) {
httpRequest.addHeader(Constants.TRACE_ID, traceId);
}
}
}Register the interceptor:
private static CloseableHttpClient httpClient = HttpClientBuilder.create()
.addInterceptorFirst(new HttpClientTraceIdInterceptor())
.build();OkHttp interceptor :
public class OkHttpTraceIdInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
String traceId = MDC.get(Constants.TRACE_ID);
Request request = chain.request();
if (traceId != null) {
request = request.newBuilder().addHeader(Constants.TRACE_ID, traceId).build();
}
return chain.proceed(request);
}
}Register the interceptor:
private static OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new OkHttpTraceIdInterceptor())
.build();RestTemplate interceptor :
public class RestTemplateTraceIdInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] body, ClientHttpRequestExecution execution) throws IOException {
String traceId = MDC.get(Constants.TRACE_ID);
if (traceId != null) {
httpRequest.getHeaders().add(Constants.TRACE_ID, traceId);
}
return execution.execute(httpRequest, body);
}
}Register the interceptor:
restTemplate.setInterceptors(Arrays.asList(new RestTemplateTraceIdInterceptor()));For third‑party services, add a similar interceptor on the server side to read the traceId from the request header and put it into MDC.
Finally, ensure the log pattern contains %X{traceId} so that every log entry prints the propagated trace identifier.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
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.