How to pass MDC context from calling thread to new thread in Java

MDC manages contextual information per thread basis, information like userID, IP mostly gets stored in MDC context. In this post we will see how to pass this information to new thread created from main thread.

MDC - Mapped Diagnostic Context

MDC is nothing but a Map, in which application stored the details as a key-value pair, which later can be used by different logging libraries while printing logs

For example if you use logback as a logging library in your java application, and you have configured below as a log pattern in logback config xml

Note: this is not how you configure logback, this is just an excerpt from the actual configuration file.

<pattern>%X{CLIENT_IP}</pattern>

then while printing logs logback will look for value of CLIENT_IP key in the MDC context, and what ever value will be stored against CLIENT_IP key that value will be printed instead of %X{CLIENT_IP} token.

Also this MDC context is per thread basis, so if you have store some key value pair in MDC context in your main thread, and you execute some other code in separate thread then there will be new MDC context for that thread.

So in this case you have to make sure you copy or pass the main thread’s MDC context to new thread’s MDC context, so that if your code which is running in new thread logs something then in those logs too the actual value of CLIENT_IP is printed in the logs.

Passing MDC context to new thread

Generally in Java application we create implementation of Runnable or Callable interface and use it to run code in separate thread. So in that case we can use below implementation to copy MDC context in new thread from calling thread.

Below wrapper class can be used when we want to create new Runnable implementation.

public class MDCWrappedRunnable implements Runnable {

    private final Runnable runnable;
    private final Map<String, String> context;

    public MDCWrappedRunnable(Runnable runnable) {
        this.runnable = runnable;
        context = MDC.getCopyOfContextMap();
    }

    @Override
    public void run() {
        try {
            MDC.setContextMap(this.context);
            runnable.run();
        } finally {
            MDC.clear();
        }
    }
}

Below wrapper class can be used when we want to create new Callable implementation.

class MDCWrappedCallable<T> implements Callable<T> {

    private final Callable<T> callable;
    private final Map<String, String> context;

    public MDCWrappedCallable(Callable<T> callable) {
        this.callable = callable;
        context = MDC.getCopyOfContextMap();
    }

    @Override
    public T call() throws Exception {
        T val = null;
        try {
            MDC.setContextMap(this.context);
            val = callable.call();
        } finally {
            MDC.clear();
        }
        return val;
    }
}

And below is the example of how you can use it.

public class Test {

    public static void main(String[] args) {
        Runnable mdcWrappedRunnable = new MDCWrappedRunnable(() ->{
            // Code which needs to be executed in new thread
        });

        Callable<String> mdcWrappedCallable = new MDCWrappedCallable<String>(() ->{
            // Code which needs to be executed in new thread
            return "";
        });
    }
}

This blog is open-source on Github.