Introduction
In modern Java microservice architectures, a single user request can traverse multiple services. Without proper logging and traceability, debugging and observability become nightmares.
This is where Logging and Correlation ID Patterns come in. These patterns ensure that every request is traceable end-to-end, even across service boundaries.
In this guide, you’ll learn how to implement these patterns in Spring Boot, how to propagate correlation IDs across services, and how to make logs developer- and ops-friendly.
🧠 What Are Logging and Correlation ID Patterns?
Logging Pattern
Ensures that all operations (success or failure) are logged with sufficient context—like user ID, request path, service name, and timestamp.
Correlation ID Pattern
Assigns a unique ID to each request that’s propagated across services and included in all log entries.
UML-Style Request Trace
[Client]
| (HTTP Header: X-Correlation-ID)
v
[API Gateway] --> [Service A] --> [Service B] --> [Database]
| | |
[Logs] [Logs] [Logs]
(with X-Correlation-ID)
👥 Core Participants
- Correlation ID Generator: Assigns ID to new requests.
- Logging Context (MDC): Stores correlation ID per thread.
- Log Appender: Outputs formatted logs (e.g., JSON).
- Log Aggregator: Collects logs (ELK, Loki, etc.)
- Tracer (optional): Used with Zipkin, Sleuth, or OpenTelemetry.
🌍 Real-World Use Cases
- Trace user actions across microservices.
- Debug failed transactions that span services.
- Identify latency bottlenecks.
- Ensure audit trails for compliance.
🧰 Java Implementation (Spring Boot + MDC + Sleuth)
1. Add Dependencies
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
2. Configure Logging Format
application.yml
spring:
sleuth:
propagation-keys: X-Correlation-ID
sampler:
probability: 1.0
3. Add Filter to Inject Correlation ID
@Component
public class CorrelationIdFilter extends OncePerRequestFilter {
private static final String CORRELATION_ID_HEADER = "X-Correlation-ID";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String correlationId = request.getHeader(CORRELATION_ID_HEADER);
if (correlationId == null) {
correlationId = UUID.randomUUID().toString();
}
MDC.put(CORRELATION_ID_HEADER, correlationId);
response.setHeader(CORRELATION_ID_HEADER, correlationId);
try {
filterChain.doFilter(request, response);
} finally {
MDC.remove(CORRELATION_ID_HEADER);
}
}
}
✅ Pros and Cons
Pros | Cons |
---|---|
Full traceability across services | Slight performance overhead |
Improves debuggability and audit trails | Misuse can expose sensitive data in logs |
Works with distributed tracing tools | Requires disciplined logging practices |
❌ Anti-Patterns and Misuse
- Not passing correlation ID downstream.
- Logging user PII or secrets.
- Overusing logs (e.g., logging entire objects).
- No standardization in log format.
🔁 Comparison with Similar Patterns
Pattern | Purpose |
---|---|
Correlation ID | Trace request across services |
Trace ID (Zipkin) | Used for distributed tracing |
MDC Logging | Adds context to each log entry |
Structured Logging | Format logs for searchability (JSON) |
💻 Log Format Example
{
"timestamp": "2025-08-06T11:00:00",
"level": "INFO",
"service": "order-service",
"correlationId": "c3f9d2fa-7891-4fd9-bd87-19eafe6f1a00",
"message": "Order placed successfully",
"userId": "42"
}
🔧 Refactoring Legacy Code
Before
log.info("Order created");
After
log.info("Order created with correlationId {}", MDC.get("X-Correlation-ID"));
Or use MDC configuration to include it automatically in log pattern:
logging.pattern.level=%5p [${X-Correlation-ID:-}]
🌟 Best Practices
- Always include correlation ID in all log entries.
- Use MDC to inject logging context.
- Use JSON logs with unique service name and trace ID.
- Never log passwords or secrets.
- Use centralized logging (e.g., ELK, Loki, Datadog).
🧠 Real-World Analogy
Think of a courier tracking number (correlation ID). No matter how many hands your package goes through, the tracking number stays the same. It helps you trace every step—from pickup to delivery.
☕ Java Feature Relevance
- MDC (Mapped Diagnostic Context): Adds correlation ID to logs.
- Servlet Filters: Modify request/response at the entry point.
- Lambdas: Use with MDC-aware logging in async tasks.
🔚 Conclusion & Key Takeaways
Logging and Correlation ID Patterns bring clarity, traceability, and accountability to your Java microservices. In distributed environments, they're no longer optional—they're essential.
✅ Summary
- Use correlation ID to trace requests end-to-end.
- Implement with MDC and servlet filters in Spring Boot.
- Include correlation ID in every log line.
- Use structured logs and centralized log aggregation.
❓ FAQ – Logging and Correlation ID Patterns
1. What is a correlation ID?
A unique ID assigned to a request to trace it across services.
2. What’s the difference between trace ID and correlation ID?
Trace ID is part of distributed tracing (Zipkin). Correlation ID is application-specific and often manually propagated.
3. How do I generate a correlation ID?
Use UUID.randomUUID()
if not passed from upstream.
4. Is MDC thread-safe?
Yes, MDC uses thread-local storage, but be careful with async operations.
5. Can I use correlation ID in async code?
Yes, but you must manually propagate MDC context using wrappers.
6. Is Spring Cloud Sleuth enough for this?
Yes, it handles correlation and trace IDs automatically.
7. Should I log correlation ID manually?
You can use log patterns or interceptors to auto-inject it.
8. How do I visualize correlation ID logs?
Use Kibana, Grafana Loki, or any centralized logging system.
9. Can I use correlation ID in non-HTTP systems?
Yes—gRPC, messaging queues, etc. can carry it in metadata/headers.
10. What’s a good default header for correlation ID?
X-Correlation-ID
is the de facto standard.