Mastering MDC in Java: Solve TraceId Loss in Multithreaded and HTTP Calls
This article explains what MDC (Mapped Diagnostic Context) is, outlines its API and advantages, demonstrates common traceId loss problems in child threads and HTTP calls, and provides practical solutions including thread‑pool wrappers, MDC utilities, and interceptors for HttpClient, OkHttp, RestTemplate, and third‑party services, plus the required log pattern.
MDC Introduction
MDC (Mapped Diagnostic Context) is a feature provided by log4j, logback, and log4j2 that allows storing key‑value pairs in a hash map bound to the current thread, making the data accessible to any code executed in that thread. Child threads inherit the parent’s MDC, and applications typically populate MDC at the start of a request.
API
clear() – remove all entries
get(String key) – retrieve value for a key
getContext() – obtain the MDC map
put(String key, Object o) – store a key‑value pair
remove(String key) – delete a specific entry
Advantages
Cleaner code and unified log format; no need to manually concatenate traceId in each log statement.
MDC Usage
Add an interceptor to populate MDC with traceId.
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 = TraceIdUtil.getTraceId();
}
MDC.put(Constants.TRACE_ID, traceId);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
MDC.remove(Constants.TRACE_ID);
}
}The simple usage works, but traceId may be lost in certain scenarios.
Problems with MDC
TraceId disappears in child‑thread logs.
TraceId disappears in HTTP calls.
Solving Child‑Thread TraceId Loss
Thread pool wrapper
Wrap tasks submitted to a thread pool so that the MDC context is copied to the worker thread.
public class ThreadPoolExecutorMdcWrapper extends ThreadPoolExecutor {
// constructors omitted for brevity
@Override
public void execute(Runnable task) {
super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public Future<?> submit(Runnable task) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
}ThreadMdcUtil provides the actual wrapping logic.
public class ThreadMdcUtil {
public static void setTraceIdIfAbsent() {
if (MDC.get(Constants.TRACE_ID) == null) {
MDC.put(Constants.TRACE_ID, TraceIdUtil.getTraceId());
}
}
public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> 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<String, String> context) {
return () -> {
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try {
runnable.run();
} finally {
MDC.clear();
}
};
}
}HTTP Call TraceId Loss
When calling third‑party services, add the traceId to the request header and extract it on the server side.
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 with
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);
}
}Add with
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);
}
}Configure with
restTemplate.setInterceptors(Collections.singletonList(new RestTemplateTraceIdInterceptor())).
Third‑party service interceptor
On the receiving side, extract the traceId from the request header and put it into MDC.
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);
}
}Log pattern
<property name="pattern">[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>Include %X{traceId} in the pattern to output the traceId.
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.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
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.
