Preserve custom MDC attributes during exception-handling in Spring Boot
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
Mapped Diagnostic Context (MDC) is a thread-local map in SLF4J/Logback that attaches contextual information (request ID, user ID, correlation ID) to every log statement. In Spring Boot, MDC values set during request processing are often lost by the time they reach @ControllerAdvice exception handlers, because the exception handling may run in a different context or the MDC is cleared prematurely. Preserving MDC attributes through the exception-handling chain requires careful placement of filters, proper @ControllerAdvice configuration, and awareness of thread boundaries.
How MDC Works
In logback-spring.xml, reference MDC values:
%X{requestId} outputs the MDC value. If the MDC is empty at log time, the value is blank.
The Problem: MDC Lost in Exception Handlers
This happens when MDC is cleared in a filter's finally block before the exception handler runs, or when async processing switches threads.
Solution 1: Set MDC in a Filter (Correct Order)
Place the MDC filter at the outermost level so it wraps the entire request lifecycle, including exception handling:
The key insight: filterChain.doFilter() includes the entire Spring MVC dispatch cycle, including @ControllerAdvice handlers. So MDC values set before doFilter() and cleared in finally after doFilter() are available throughout exception handling.
Solution 2: HandlerInterceptor with afterCompletion
Register it:
afterCompletion is called after the response is fully rendered, including after @ExceptionHandler methods run.
Solution 3: MDC-Aware Exception Handler
If you cannot control the filter order, copy MDC values into the exception itself:
Async Thread Propagation
MDC is thread-local, so it is lost when processing moves to a different thread:
Common Pitfalls
- Clearing MDC too early in a filter: If
MDC.clear()runs in afinallyblock inside a controller or service method rather than in the outermost filter, exception handlers lose the MDC context. Always clear MDC in the outermost filter'sfinallyblock. - Filter ordering: If your MDC filter does not have
@Order(Ordered.HIGHEST_PRECEDENCE), other filters may run first and the MDC may not be set when exceptions occur in those filters. Ensure the MDC filter wraps everything. - Losing MDC across async boundaries:
@Asyncmethods,CompletableFuture.supplyAsync(), and reactive streams run on different threads where MDC is empty. UseTaskDecoratoror manually copy the MDC context map. - Not cleaning up MDC: Servlet containers reuse threads. If you do not call
MDC.clear()after each request, MDC values from a previous request leak into the next request on the same thread, producing incorrect log context. - Assuming
@ControllerAdviceruns on the same thread: While synchronous Spring MVC typically uses the same thread for the entire request (including exception handlers), reactive (WebFlux) and async scenarios may switch threads, dropping MDC values.
Summary
- MDC values are thread-local and must be set before and cleared after the entire request lifecycle
- Place MDC setup in a servlet filter with
@Order(Ordered.HIGHEST_PRECEDENCE)so it wraps exception handling filterChain.doFilter()includes@ControllerAdviceexecution, so MDC set before it is available in exception handlers- For async processing, use
MdcTaskDecoratorto copy MDC context to worker threads - Always call
MDC.clear()in afinallyblock to prevent context leaking between requests - Consider capturing MDC in custom exceptions as a fallback when filter ordering cannot be controlled
Related reading
- Prevent ArgoCD from syncing a single ressource
- Prevent stack trace logging for custom exception in Spring Boot application
- Printing not being logged by Kubernetes
- Printing the loss during TensorFlow training
- Pretty-Print JSON in Java
- Pretty print JSON output of Spring Boot Actuator endpoints
- Preventing Exceptions from 3rd party component from crashing the entire application
- Print current call stack from a method in code

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.