Backend Development 12 min read

MDC and TraceId Propagation in Java Backend Applications

This article explains what MDC is, the problems it faces such as traceId loss in child threads and HTTP calls, and provides concrete solutions including interceptor implementation, thread‑pool wrappers, and logging pattern adjustments to ensure reliable traceId propagation in Java backend services.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
MDC and TraceId Propagation in Java Backend Applications

Introduction

Through this article you will learn what MDC is, the issues that arise when using it, and how to solve those problems.

MDC Overview

Brief

MDC (Mapped Diagnostic Context) is a feature provided by log4j, logback, and log4j2 that allows convenient logging in multi‑threaded environments. MDC can be seen as a hash table bound to the current thread, where key‑value pairs can be stored and accessed by any code executing in the same thread.

Child threads inherit the MDC content of their parent thread. When logging, you simply retrieve the needed information from MDC. Typically, a web application stores data in MDC at the very beginning of a request.

API

clear() ⇒ remove all entries from MDC

get(String key) ⇒ get the value of the specified key in the current thread's MDC

getContext() ⇒ obtain the entire MDC map of the current thread

put(String key, Object o) ⇒ store a key‑value pair in the current thread's MDC

remove(String key) ⇒ delete the specified key from the current thread's MDC

Advantages

The code stays clean, log style is unified, and you no longer need to manually concatenate the traceId in each log statement, e.g. LOGGER.info("traceId:{} ", traceId) .

Using MDC

Adding 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);
    }
}

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>

In some cases the traceId may be lost.

Problems with MDC

Child thread logs lose the traceId.

HTTP calls lose the traceId.

Solutions

Child‑Thread TraceId Loss

Wrap thread‑pool tasks so that the MDC context is transferred to the child thread.

Thread‑pool wrapper: ThreadPoolExecutorMdcWrapper.java

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()));
    }
}

Thread‑MDC utility: ThreadMdcUtil.java

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();
            }
        };
    }
}

HTTP Call TraceId Loss

When invoking third‑party services, add the traceId to the request header and provide an interceptor on the receiving side to put it into MDC.

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()));

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);
    }
}

Finally, ensure the logging pattern contains %X{traceId} so that the traceId is printed with each log entry.

Thank you for reading, hope it helps :) Source: juejin.cn/post/6844904101483020295
backendJavaThreadPoolloggingTraceIdHTTP InterceptorMDC
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.