Distributed tracing in Spring Boot 3 (Micrometer Tracing + OpenTelemetry/Zipkin, not Sleuth), following a request across services, service discovery options (Eureka, Consul, Kubernetes-native) and their configuration, client-side vs server-side discovery, load balancers with Kubernetes and cloud (Services, Ingress/Gateway API, cloud LBs), what a service mesh is, and resilience tooling — with a Payment-service circuit-breaker scenario.
Published September 25, 2026
These questions ask "what have you actually used?", so name concrete tools, and today's versions:
Then show you understand the mechanics underneath: trace context propagation, the circuit-breaker states, and client-side vs server-side discovery.
Short answer: Use distributed tracing:
traceparent headers (and in message headers for Kafka).In Spring Boot 3:
micrometer-tracing-bridge-otel (or -brave) plus an exporter: OTLP to an OpenTelemetry Collector → Jaeger/Tempo, or directly to Zipkin.RestClient/WebClient/RestTemplate (when built from the auto-configured builders), MVC/WebFlux, @Scheduled, Kafka (with observation enabled), JDBC (through datasource-micrometer), and more.management:
tracing:
sampling:
probability: 0.1 # 10% in production; 1.0 in development. Or tail-based sampling in the collector
otlp:
tracing:
endpoint: http://otel-collector:4318/v1/traces
logging:
pattern:
correlation: "[${spring.application.name:},%X{traceId:-},%X{spanId:-}] "
Common trap: answering with Spring Cloud Sleuth. It doesn't support Boot 3; its functionality moved to Micrometer Tracing.
Key points to cover:
Observation API (@Observed).Learn it in depth → Distributed Tracing
Short answer: Combine the three pillars, linked together by the trace ID:
traceId, so you can jump from a trace to its logs in Kibana or Loki.The ID is created at the edge (the gateway), or accepted from the client. It's propagated automatically by the instrumented clients, and by message headers for asynchronous hops, so a Kafka consumer's span continues the producer's trace.
Key points to cover:
Short answer: Name the two you've used, with their setup:
@EnableEurekaServer, clustered for HA).spring-cloud-starter-netflix-eureka-client, set spring.application.name, and set eureka.client.service-url.defaultZone.@LoadBalanced builders.spring-cloud-starter-consul-discovery, and set spring.cloud.consul.host/port.http://inventory.shop.svc.cluster.local), and kube-proxy balances across the ready pods.DiscoveryClient backed by the Kubernetes API) is optional. It needs RBAC permissions to list Services and Endpoints.# Eureka client
spring:
application:
name: order-service
eureka:
client:
service-url:
defaultZone: http://eureka-1:8761/eureka,http://eureka-2:8761/eureka
instance:
prefer-ip-address: true
Learn it in depth → Service Discovery
Short answer:
Key points to cover:
Short answer: There are layers of load balancing:
Steps for NGINX Ingress:
type: LoadBalancer, which provisions the cloud load balancer and a public IP.apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "30"
spec:
ingressClassName: nginx
tls: [{ hosts: [api.shop.example], secretName: shop-tls }]
rules:
- host: api.shop.example
http:
paths:
- { path: /orders, pathType: Prefix, backend: { service: { name: order-service, port: { number: 80 } } } }
Key points to cover:
ingress-nginx controller has been retired, so plan for the migration.Learn it in depth → Services & Ingress
Short answer: A dedicated infrastructure layer for service-to-service communication. Proxies (Envoy sidecars, or Istio's ambient mode with node-level proxies; Linkerd uses its own micro-proxy) intercept all the traffic, and a control plane configures them. It gives you, without application code changes:
The costs:
It's worth it with many services and polyglot stacks, or strict zero-trust requirements.
Common trap: duplicating retries in both the application (Resilience4j) and the mesh. The retries multiply, and can overload a struggling service. Decide which layer owns which policy.
Learn it in depth → Design Service Mesh Basics
Short answer: Resilience4j (through Spring Cloud CircuitBreaker, or its Spring Boot starter), combining:
At the platform level:
Learn it in depth → Circuit Breaker Pattern
Short answer: Scenario: during a sale, the external payment provider slows down to 20 s responses. The Order service's request threads pile up waiting, its connection pool drains, and checkout, and then the whole site, stops responding. That's a cascading failure.
Solution:
PAYMENT_PENDING, queue the payment for asynchronous retry, and tell the user "we'll confirm shortly". Or offer another payment method.@Service
class PaymentAdapter {
private final PaymentClient client;
PaymentAdapter(PaymentClient client) { this.client = client; }
@CircuitBreaker(name = "payments", fallbackMethod = "deferPayment")
@Bulkhead(name = "payments")
@TimeLimiter(name = "payments") // requires an async return type
public CompletableFuture<PaymentResult> authorize(PaymentRequest req) {
return CompletableFuture.supplyAsync(() -> client.authorize(req));
}
private CompletableFuture<PaymentResult> deferPayment(PaymentRequest req, Throwable cause) {
pendingPayments.enqueue(req); // retried later by a worker; the order stays PAYMENT_PENDING
return CompletableFuture.completedFuture(PaymentResult.pending());
}
}
resilience4j:
circuitbreaker:
instances:
payments:
sliding-window-type: COUNT_BASED
sliding-window-size: 50
failure-rate-threshold: 50
slow-call-rate-threshold: 80
slow-call-duration-threshold: 2s
wait-duration-in-open-state: 30s
permitted-number-of-calls-in-half-open-state: 5
record-exceptions: [java.io.IOException, java.util.concurrent.TimeoutException]
ignore-exceptions: [com.shop.payments.CardDeclinedException] # business errors must not trip the breaker
Key points to cover:
Q: What's the order of the Resilience4j decorators, and why does it matter? A: The default aspect order (outermost first) is: Retry → CircuitBreaker → RateLimiter → TimeLimiter → Bulkhead → the call. So retries see the breaker's fast failures, and every retry attempt counts against the breaker. Configure the aspect order if you need different semantics.
Q: How do you propagate trace context through Kafka?
A: Spring Kafka with observation enabled (spring.kafka.template.observation-enabled=true, spring.kafka.listener.observation-enabled=true) writes traceparent into the record headers, and continues it in the consumer, so the consumer's span links to the producer's trace.
Q: Eureka vs Kubernetes Services: when would you still use Eureka? A: When services run outside Kubernetes (VMs, mixed estates), or across several clusters and environments without a mesh, or when you need client-side, zone-aware balancing logic. On one Kubernetes platform, native Services plus DNS are simpler.
Q: What's the difference between liveness and readiness in resilience terms? A: Readiness failing removes the pod from the load balancer (temporary: dependencies warming up, overload). Liveness failing restarts the pod (it's stuck). Don't include external dependencies in liveness, or a database outage will restart every pod in a loop.