MDC (Mapped Diagnostic Context) in Java: Introduction, API, Advantages, and Practical Solutions for TraceId Propagation
This article introduces MDC, explains its API and benefits, and provides concrete solutions—including a thread‑pool wrapper and HTTP client interceptors—to reliably propagate traceId across threads and remote calls in Java backend applications.
This article gives a comprehensive overview of MDC (Mapped Diagnostic Context), a feature provided by log4j, logback, and log4j2 that allows storing key‑value pairs bound to the current thread for easier logging in multi‑threaded environments.
1. Introduction
MDC works like a thread‑local hash map; child threads inherit the parent’s MDC content. In a web request, MDC is typically populated at the beginning of request processing.
2. API
clear(): remove all entries
get(String key): retrieve the value for a key
getContext(): obtain the current MDC map
put(String key, Object value): store a key‑value pair
remove(String key): delete a specific entry
3. Advantages
Using MDC makes log statements concise and uniform, e.g., LOGGER.info("traceId:{} ", traceId) , without manually concatenating the traceId in each log.
4. Practical Usage
4.1 Add an interceptor
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Use upstream traceId 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);
}
}4.2 Modify log pattern
<property name="pattern">[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>5. Problems with MDC
TraceId lost in child threads
TraceId lost in HTTP calls
These issues are solved step by step without premature optimization.
6. Solution for child‑thread loss
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()));
}
}7. MDC utility 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();
}
};
}
}8. HTTP‑call traceId propagation
For HttpClient, OkHttp, and RestTemplate, custom interceptors add the traceId header and the downstream service can read it back into MDC.
8.1 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);
}
}
}Adding the interceptor:
private static CloseableHttpClient httpClient = HttpClientBuilder.create()
.addInterceptorFirst(new HttpClientTraceIdInterceptor())
.build();8.2 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);
}
}Adding the interceptor:
private static OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new OkHttpTraceIdInterceptor())
.build();8.3 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);
}
}Adding the interceptor:
restTemplate.setInterceptors(Arrays.asList(new RestTemplateTraceIdInterceptor()));All three solutions ensure that the traceId is carried through asynchronous tasks and remote HTTP calls, allowing end‑to‑end request tracing in backend services.
Finally, the article reminds readers to include %X{traceId} in the log pattern to actually print the traceId.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
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.