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.
0 Comments