Backend Development 11 min read

Understanding and Implementing MDC (Mapped Diagnostic Context) in Java Logging

This article introduces MDC, explains its API and advantages, and provides practical solutions for traceId propagation in multithreaded environments, HTTP calls, and custom thread pools using Java logging frameworks such as Log4j, Logback, and Log4j2.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Understanding and Implementing MDC (Mapped Diagnostic Context) in Java Logging

MDC Introduction

MDC (Mapped Diagnostic Context) is a feature provided by log4j, logback, and log4j2 that allows you to store key‑value pairs bound to the current thread, making them accessible throughout the thread's execution. Child threads inherit the MDC of their parent, enabling consistent logging information such as traceId.

1. API Overview

clear() : Remove all entries from MDC.

get(String key) : Retrieve the value associated with the given key.

getContext() : Get 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 entry from MDC.

2. Advantages

Using MDC makes code concise and logs uniform; you no longer need to manually concatenate traceId in each log statement, e.g., LOGGER.info("traceId:{} ", traceId) .

MDC Usage

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

2. Modifying Log Pattern

<property name="pattern">[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>

The key %X{traceId} must match the MDC key name.

Problems with MDC

TraceId lost in child thread logs.

TraceId lost during HTTP calls.

Solutions

Child Thread TraceId Loss

Wrap tasks submitted to thread pools so that the MDC context is transferred.

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

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

For HTTP client libraries, add interceptors that copy the traceId from MDC into request headers.

HttpClient

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

OkHttp

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

RestTemplate

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

Third‑party services must also add a similar interceptor to read the traceId from incoming headers and put it into MDC.

Final Note

The author encourages readers to like, share, and follow the "Code Monkey Technical Column" public account for more advanced Spring Cloud, Spring Boot, and MyBatis tutorials.

backendJavaThreadPoolloggingTraceIdHTTP InterceptorMDC
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

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.