java xml 91 lines · 3 tabs

Propagate a Correlation ID Through Servlet Requests With MDC and a Feign Interceptor

Shared by codesnips Aug 2026
3 tabs
package com.example.tracing;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.UUID;
import java.util.regex.Pattern;

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorrelationIdFilter implements Filter {

    public static final String HEADER = "X-Correlation-Id";
    public static final String MDC_KEY = "correlationId";

    private static final Pattern VALID = Pattern.compile("[A-Za-z0-9\\-]{8,64}");

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        String correlationId = resolve(request.getHeader(HEADER));
        MDC.put(MDC_KEY, correlationId);
        response.setHeader(HEADER, correlationId);
        try {
            chain.doFilter(request, response);
        } finally {
            MDC.remove(MDC_KEY);
        }
    }

    private String resolve(String incoming) {
        if (incoming != null && VALID.matcher(incoming).matches()) {
            return incoming;
        }
        return newId();
    }

    private String newId() {
        return UUID.randomUUID().toString();
    }
}
3 files · java, xml Explain with highlit

This snippet shows how a correlation ID flows through a request-handling stack so that every log line for a single request can be tied together, even across thread boundaries and outbound HTTP calls. The core idea is to extract or generate an identifier once at the edge of the system and store it in SLF4J's Mapped Diagnostic Context (MDC), which is a per-thread key/value map that the logging pattern can reference automatically.

In CorrelationIdFilter, the servlet filter reads an incoming X-Correlation-Id header. If the caller supplies one, it is validated with a conservative regex to avoid log-forging or unbounded values; otherwise a fresh UUID is generated with newId(). The value is placed into MDC under a stable key and also written back onto the response so clients and gateways can observe it. The crucial detail is the try/finally block: MDC.remove(MDC_KEY) must run no matter what, because servlet containers pool and reuse threads, and a stale value would leak into an unrelated later request. Registering the filter with @Order(HIGHEST_PRECEDENCE) ensures the ID is set before any downstream logging occurs.

logback-spring.xml wires the value into output. The pattern references %X{correlationId} and defaultValue renders a placeholder when no ID is present (for example, on startup logs that run outside any request). Because the appender reads MDC at format time, no logging call site needs to know about correlation IDs at all — instrumentation stays out of business code.

FeignCorrelationInterceptor closes the loop for outbound calls. When a service calls another service via Feign, the interceptor copies the current MDC value into the X-Correlation-Id header of the outgoing request, so the downstream service's own CorrelationIdFilter will pick it up and continue the same trace.

The main trade-off with MDC is its thread affinity: work handed to a different thread (an @Async method, a thread pool, a reactive scheduler) does not inherit the context automatically, so a TaskDecorator or manual copy is needed there. Used at the servlet edge, though, this pattern is cheap, dependency-light, and dramatically improves log correlation.


Related snips

Share this code

Here's the card — post it anywhere.

Propagate a Correlation ID Through Servlet Requests With MDC and a Feign Interceptor — share card
Link copied