Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

I need to log a few attributes of the request like the request id and the locale, but when using parallelStream, it seems that the ThreadLocal of the MDC looses the information.

Ive analyzed the solution of passing the MDC context between the threads when creating the parallelStream but it seems dirty and I also have a lot of usages of the parallelStream.

Is there any other way of doing this?

Thank you

The only solution I found, is to copy the context into a final variable outside of the stream and apply it for every single iteration:

Map<String, String> contextMap = MDC.getCopyOfContextMap();
Stream.iterate(0, i -> i + 1).parallel()
    .peek(i -> MDC.setContextMap(contextMap))
    // ...logic...
    // in case you're using a filter, you need to use a predicate and combine it with a clear step:
    filter(yourPredicate.or(i -> {
                MDC.clear();
                return false;
    // clear right before terminal operation
    .peek(i -> MDC.clear())
    .findFirst();
// since the initial thread is also used within the stream and the context is cleared there, 
// we need to set it again to its initial state
MDC.setContextMap(contextMap);    

The cost for that solution is 1) a few microseconds per 100 iterations and 2) worse readability, but both are acceptable:

  • This is a benchmark comparing an IntStream.range(0, 100).parallel().sum() (=baseline) with same stream that uses that MDC copy logic:
  • Benchmark               Mode  Cnt   Score   Error   Units
    MDC_CopyTest.baseline  thrpt    5   0,038 ± 0,005  ops/us
    MDC_CopyTest.withMdc   thrpt    5   0,024 ± 0,001  ops/us
    MDC_CopyTest.baseline   avgt    5  28,239 ± 1,308   us/op
    MDC_CopyTest.withMdc    avgt    5  40,178 ± 0,761   us/op
    
  • To improve readability, it can be wrapped into a small helper class:
  • public class MDCCopyHelper {
        private Map<String, String> contextMap = MDC.getCopyOfContextMap();
        public void set(Object... any) {
            MDC.setContextMap(contextMap);
        public void clear(Object... any) {
            MDC.clear();
        public boolean clearAndFail() {
            MDC.clear();
            return false;
    

    The streaming code looks then a bit nicer:

    MDCCopyHelper mdcHelper = new MDCCopyHelper();
    try {
        Optional<Integer> findFirst = Stream.iterate(0, i -> i + 1)
            .parallel()
            .peek(mdcHelper::set)
            // ...logic...
            // filters predicates should be combined with clear step
            .filter(yourPredicate.or(mdcHelper::clearAndFail))
            // before terminal call:
            .peek(mdcHelper::clear)
            .findFirst();
    } finally {
        // set the correct MDC at the main thread again
        mdcHelper.set();
                    What if an exception is thrown inside the logic? Wouldn't it leave the MDC dirty? Did you manage to come up with a solution considering exceptions?
    – fasfsfgs
                    Nov 18, 2019 at 15:12
                    Be careful with the .peek(mdcHelper::clear) at the end. Everything after the parallel() is executed in multiple threads, but this includes the main thread. The main thread does not idle, it is actively used. So you can end up with an empty MDC in the main thread.
    – adiesner
                    Jul 5, 2022 at 16:32
                    @adiesner yes, you are right. That's why I call mdcHelper.set(); after the stream execution. The mdcHelper holds a copy of the MDC and puts the data back to the main thread's MDC.
    – rudi
                    Jul 13, 2022 at 7:53
                    I wasn't aware of that, so I skipped the last command in my code, since I thought that is a useless extra command. When the parallel stream can throw an exception there is also a chance that the MDC is not restored in the main thread after the stream execution.
    – adiesner
                    Jul 13, 2022 at 11:11
    

    My solution is to wrap those's functional interface. Similar to the static proxy pattern.
    For example

    public static void main(String[] args) {
        System.err.println(Thread.currentThread().getName());
        String traceId = "100";
        MDC.put("id", traceId);
        System.err.println("------------------------");
        Stream.of(1, 2, 3, 4)
              .parallel()
              .forEach((num -> {
                  System.err.println(Thread.currentThread().getName()+" "+ traceId.equals(MDC.get("id")));
        System.err.println("------------------------");
        Stream.of(1, 2, 3, 4)
              .parallel()
              // the key is the TraceableConsumer
              .forEach(new TraceableConsumer<>(num -> {
                  System.err.println(Thread.currentThread().getName() + " " + traceId.equals(MDC.get("id")));
    
    public class TraceableConsumer<T> extends MDCTraceable implements Consumer<T> {
        private final Consumer<T> target;
        public TraceableConsumer(Consumer<T> target) {
            this.target = target;
        @Override
        public void accept(T t) {
            setMDC();
            target.accept(t);
    
    public abstract class MDCTraceable {
        private final Long id;
        private final Long userId;
        public MDCTraceable() {
            id = Optional.ofNullable(MDC.get("id")).map(Long::parseLong).orElse(0L);
            userId = Optional.ofNullable(MDC.get("userId")).map(Long::parseLong).orElse(0L);
        public void setMDC(){
            MDC.put("id", id.toString());
            MDC.put("userId", userId.toString());
        public void cleanMDC(){
            MDC.clear();
            

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.