Using MDC for TraceId Propagation in Java Backend Applications
This article explains how to use Mapped Diagnostic Context (MDC) in Java logging frameworks to propagate traceId across threads and HTTP calls, covering API usage, advantages, common pitfalls, and practical solutions including interceptor implementation, thread‑pool wrappers, and client‑side interceptors for HttpClient, OkHttp, and RestTemplate.
Hello everyone, I am a senior architect.
1. Introduction: MDC (Mapped Diagnostic Context) is a feature provided by log4j, logback, and log4j2 that allows storing key‑value pairs bound to the current thread, making them accessible to any code executed in the same thread. Child threads inherit the MDC of their parent, which is useful for logging request‑specific data such as traceId.
2. API Description:
clear(): remove all entries from MDC
get(String key): retrieve the value for a specific key
getContext(): obtain the entire MDC map
put(String key, Object o): store a key‑value pair
remove(String key): delete a specific entry
3. Advantages: Simplifies code, unifies log format, and eliminates manual string concatenation for traceId, e.g., LOGGER.info("traceId:{} ", traceId) .
MDC Usage
1. Add Interceptor
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// If there is an upstream call, use its ID
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 {
// Remove after request finishes
MDC.remove(Constants.TRACE_ID);
}
}2. Modify Log Pattern
<property name="pattern">[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>The key part is %X{traceId} , which must match the MDC key name.
Problems with MDC
TraceId lost in child thread logs
TraceId lost in HTTP calls
These issues are solved incrementally.
Child Thread Log TraceId Loss
Solution: wrap thread‑pool tasks to copy MDC context.
public class ThreadPoolExecutorMdcWrapper extends ThreadPoolExecutor {
// 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()));
}
}ThreadMdcUtil
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();
}
};
}
}Explanation: The wrapper copies the parent thread’s MDC map to the child thread, ensures traceId exists, runs the task, and finally clears MDC.
HTTP Call TraceId Loss
When making HTTP calls to third‑party services, the traceId must be added to request headers and extracted on the receiving side.
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);
}
}
}
private static CloseableHttpClient httpClient = HttpClientBuilder.create()
.addInterceptorFirst(new HttpClientTraceIdInterceptor())
.build();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);
}
}
private static OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new OkHttpTraceIdInterceptor())
.build();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);
}
}
restTemplate.setInterceptors(Arrays.asList(new RestTemplateTraceIdInterceptor()));4. Third‑Party Service Interceptor
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String traceId = request.getHeader(Constants.TRACE_ID);
if (traceId == null) {
traceId = TraceIdUtils.getTraceId();
}
MDC.put("traceId", traceId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
MDC.remove(Constants.TRACE_ID);
}
}All interceptors ensure the traceId is propagated and logged consistently. Finally, the log pattern must include %X{traceId} to output the traceId.
Note: The %X{traceId} placeholder is required in the log pattern.
At the end, the author offers a BAT interview question collection and a QR code for a special gift, encouraging readers to join the architecture community.
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.