How to Use MDC with ExchangeFilterFunction for Request Tracing in WebClient

Modern microservices rely on WebClient for efficient, non-blocking HTTP calls. But logging, debugging, and tracing become challenging when calling other services—especially when you need to pass contextual data like correlationId, tenantId, userId, and auth tokens.

This is where ExchangeFilterFunction, combined with MDC, becomes extremely powerful.

In this blog, you’ll learn:

What ExchangeFilterFunction is

Why it is needed

How it works internally

How MDC can inject headers (like correlationId) into every outbound request

Real-world microservice examples

Additional production use cases


Let’s dive deep.


---

⭐ What is ExchangeFilterFunction?

ExchangeFilterFunction is a WebClient feature that allows you to intercept:

Every outgoing HTTP request

Every incoming HTTP response


You can use it to:

Add headers dynamically

Log request/response payloads

Modify the request before sending

Implement centralized error handling

Inject metadata such as correlation IDs


It works just like interceptors in RestTemplate or Servlet Filters, but specifically for WebClient.


---

⭐ Why Do We Need ExchangeFilterFunction?

Here are the core reasons:


---

🔹 1. Add Common Headers Across All WebClient Calls

Example:

Authorization token

Correlation ID

Tenant ID

Platform name

User session details


Instead of setting headers in every call:

webClient.get().uri("/orders").header("correlationId", id)...

…you inject it once using a filter and reuse the WebClient cleanly.


---

🔹 2. Logging of Requests and Responses

It helps with:

Debugging issues

Tracking slow APIs

Understanding server behavior

APM/Monitoring integrations



---

🔹 3. Centralized Error Handling

Example:

Map 4xx to custom exceptions

Retry for 503 or 504

Parse error bodies

Provide fallback responses


This reduces boilerplate across your application.


---

🔹 4. Modify Outbound Requests Dynamically

Examples:

Replace authentication token

Mask sensitive data before logging

Add custom trace headers


Filters are applied in the order you configure them, giving full control.


---

⭐ Using MDC Inside ExchangeFilterFunction (Real-World Use Case)

The most common requirement in microservices is:

> “Pass correlationId in every outgoing WebClient request.”



When a request hits your service, a Correlation ID is generated and stored in MDC:

MDC.put("correlationId", "abc-123");

But for downstream calls, this ID must be sent as a header.

To solve this cleanly, use ExchangeFilterFunction.


---

🚀 Real-World Example: Injecting Correlation ID Using MDC + ExchangeFilterFunction

✔️ Add a Filter to WebClient

@Bean
public WebClient webClient() {
    return WebClient.builder()
            .filter(addCorrelationIdHeader())
            .build();
}

private ExchangeFilterFunction addCorrelationIdHeader() {
    return (request, next) -> {

        String correlationId = MDC.get("correlationId");

        ClientRequest filteredRequest = ClientRequest.from(request)
                .header("correlationId", correlationId != null ? correlationId : UUID.randomUUID().toString())
                .build();

        return next.exchange(filteredRequest);
    };
}

✔ What this does:

Reads correlationId from MDC

Adds it as a request header

Ensures all downstream services receive it

Maintains end-to-end traceability


Now all services log:

correlationId=abc-123

regardless of async calls or external API requests.


---

⭐ More Advanced Use Cases of ExchangeFilterFunction


---

🔹 1. Log Request and Response Bodies

private ExchangeFilterFunction logRequest() {
    return ExchangeFilterFunction.ofRequestProcessor(req -> {
        log.info("Sending Request: {} {}", req.method(), req.url());
        return Mono.just(req);
    });
}

private ExchangeFilterFunction logResponse() {
    return ExchangeFilterFunction.ofResponseProcessor(resp -> {
        log.info("Received Response: {}", resp.statusCode());
        return Mono.just(resp);
    });
}


---

🔹 2. Add Authentication Token Dynamically

Use cases:

JWT token

OAuth2 token refresh

API key rotation



---

🔹 3. Retry Logic via Filters

Implement custom retry based on response code.


---

🔹 4. Modify Body Before Sending (Advanced)

Useful for masking or dynamic request transformations.


---

⭐ Why MDC + ExchangeFilterFunction is a Powerful Combination

Both tools solve critical but different problems:

Feature MDC ExchangeFilterFunction

Stores contextual data ✔ ✖
Injects headers using context Uses data ✔
Adds logging Limited ✔
Modifies WebClient request ✖ ✔
Helps in async propagation ✔ (with TaskDecorator) ✖


Together, they provide:

End-to-end request tracing

Consistent logging across threads

Automatic header injection

Cleaner, centralized debugging


This is essential for production-grade microservices.


---

🧠 Best Practices

Always clear MDC after request completion

Use TaskDecorator for MDC propagation in async threads

Use correlationId from incoming header if present

Log both request and response for external APIs

Never log sensitive data (tokens/passwords)

Keep filters pure and stateless



---

🎉 Final Thoughts

ExchangeFilterFunction is one of the most underused yet extremely powerful features in Spring WebClient. When combined with MDC, it enables:

cleaner WebClient usage

zero-boilerplate header injection

strong distributed tracing

accurate debugging

improved observability


If you’re building microservices that communicate with each other, this pattern is essential for production readiness.

Post a Comment

0 Comments