Spring Cloud Feign Client: Declarative HTTP in Microservices
Master Spring Cloud OpenFeign: @FeignClient configuration, ErrorDecoder, RequestInterceptor for auth, Resilience4j fallbacks, and Retryer for production-grade HTTP clients.
- Declare HTTP clients with @FeignClient(name, url, fallback/fallbackFactory) and interface methods mirroring Spring MVC annotations
- Configure timeouts via FeignConfig with Request.Options(connectTimeout, readTimeout)
- Map HTTP error responses to exceptions using ErrorDecoder interface implementations
- Add auth headers to every request with @RequestInterceptor beans
- Use FallbackFactory
(not fallback) so your fallback receives the exception that triggered it
Feign is a translator that converts your Java method calls into HTTP requests. Instead of writing boilerplate code to build URLs, set headers, serialize request bodies, and parse responses, you define an interface that looks exactly like a Spring MVC controller — and Feign does all the HTTP work behind the scenes.
Every microservice team eventually writes the same boilerplate HTTP client code: URL concatenation, header setting, request body serialization, response deserialization, error status code checking, and retry logic. With RestTemplate this means hundreds of lines of infrastructure code per service integration. With WebClient it's better but still requires significant ceremony for each new endpoint.
Spring Cloud OpenFeign eliminates this ceremony entirely. You declare an interface annotated with @FeignClient, define methods that mirror the downstream service's REST API using the same Spring MVC annotations you use in controllers (@GetMapping, @PostMapping, @PathVariable, @RequestBody), and Spring Cloud generates a working HTTP client implementation at startup. The entire integration layer for a CRUD service is typically under 30 lines.
The production pain points begin when things go wrong. How does Feign handle a 404? By default it throws FeignException — which loses the HTTP status code in the stack trace and makes it impossible to distinguish 'not found' from 'server error' without parsing exception messages. ErrorDecoder solves this by mapping HTTP status codes to typed business exceptions.
Authentication is another friction point. If your downstream services require JWT Bearer tokens, API keys, or HMAC signatures, you need to add headers to every request. @RequestInterceptor lets you register beans that run before every Feign request, adding headers from the current security context, environment, or computed signatures — without touching each individual Feign method.
Resilience is the hardest part. When a downstream service is slow or unavailable, you need fallbacks. Feign integrates with Resilience4j via Spring Cloud CircuitBreaker, and the FallbackFactory pattern gives fallbacks access to the exception that triggered them — essential for distinguishing circuit-open (return cached data) from authentication failure (propagate the 401).
This guide covers all of these patterns with production code, the exact configuration that works in Spring Boot 3.x, and the debug commands you need when Feign misbehaves in production.
@FeignClient Annotation and Basic Configuration
The @FeignClient annotation marks an interface as a Feign client and provides the metadata needed to generate the HTTP client implementation. The name attribute is the logical service name used for load balancing. The url attribute bypasses service discovery and calls the specified URL directly. The configuration attribute points to a configuration class that customizes the Feign client (encoder, decoder, logger, interceptors, error decoder, timeouts). The fallback or fallbackFactory attributes specify the fallback implementation.
The interface methods must mirror the downstream service's REST API. Use @GetMapping, @PostMapping, @PutMapping, @DeleteMapping from Spring MVC. Path variables use @PathVariable, query parameters use @RequestParam, request bodies use @RequestBody, and headers use @RequestHeader. For multipart file uploads, use @RequestPart.
Name collisions between multiple Feign clients that call the same service name can cause issues. If two @FeignClient interfaces have the same name attribute but different configurations, Spring will throw a bean definition conflict. Use contextId to give each client a unique bean name while keeping the same service name for routing.
Feign is not reactive by default. For reactive applications using WebFlux, use the spring-cloud-starter-openfeign with the feign-reactor library, or prefer Spring WebClient with @LoadBalanced for fully reactive inter-service communication. Mixing synchronous Feign in a reactive stack causes thread pool pressure.
FeignConfig: Timeouts, Logging, and Custom Components
Feign configuration classes are regular Spring @Configuration classes that define beans consumed by the Feign infrastructure. Unlike most Spring beans, Feign configuration beans are scoped — if you specify a class in @FeignClient(configuration=MyConfig.class), those beans are only active for that specific client. Global defaults go in the default section of spring.cloud.openfeign.client.config YAML or in a configuration class annotated with @Configuration (without the @FeignClient reference) and placed on the component scan path.
Timeout configuration via Request.Options is the most important config. connectTimeout is the time to establish a TCP connection; readTimeout is the time to wait for a response after the connection is established. These should be sized to your downstream service's P99 response time plus a safety margin, and should be lower than any circuit breaker slow-call threshold.
Feign Logger Level controls what HTTP traffic is logged: NONE (nothing), BASIC (request method/URL and response status/time), HEADERS (plus request/response headers), FULL (plus request/response body). Use BASIC in production for audit trails and FULL only in development or when debugging a specific issue (it logs full bodies which can be large and contain sensitive data).
Custom encoder and decoder beans let you handle non-standard content types or response formats. For example, if a downstream service returns XML, add a JAXB decoder. For form-encoded requests, add a FormEncoder. The default encoder/decoder pair handles JSON via Jackson.
ErrorDecoder: Mapping HTTP Errors to Business Exceptions
Feign's default behavior for non-2xx responses is to throw a FeignException with the status code embedded in the message string. This is problematic for three reasons: (1) your business logic must parse string messages to determine the error type, (2) the exception hierarchy doesn't convey intent (a 404 and a 500 both throw FeignException), and (3) the response body (often containing error details) may not be easily accessible.
ErrorDecoder provides a hook that runs after receiving a non-2xx response. It receives a Request and Response object and returns an Exception. The returned exception is what gets thrown to your calling code. By implementing ErrorDecoder, you can map status codes to your domain exceptions, extract error details from the response body, and handle retryable errors by returning a RetryableException (which triggers Feign's Retryer).
The most important error categories to handle are: 400 (Bad Request — deserialize the error body into your error response DTO and throw a ValidationException), 401 (Unauthorized — throw an AuthenticationException so the caller can refresh tokens), 403 (Forbidden — throw an AccessDeniedException), 404 (Not Found — throw a ResourceNotFoundException with the resource type and ID), 429 (Too Many Requests — return a RetryableException with the Retry-After header value), and 5xx (Service errors — throw a ServiceException and optionally return a RetryableException for idempotent operations).
RetryableException is special — when ErrorDecoder returns a RetryableException, Feign's configured Retryer will retry the request. Use this for transient errors (502, 503, 504, connection resets) but NEVER for POST/PUT/DELETE unless you have idempotency guarantees.
@RequestInterceptor for Authentication Headers
RequestInterceptor provides a pre-request hook that runs before every Feign request made by clients that include it in their configuration. It receives a RequestTemplate that you can modify to add headers, change the URL, add query parameters, or modify the request body. This is the canonical way to add authentication tokens to all Feign requests without cluttering individual method signatures with @RequestHeader parameters.
For JWT propagation, the interceptor reads the current user's JWT from the SecurityContext and adds it as a Bearer token. This works for synchronous request flows where the SecurityContext is populated. The critical caveat is async execution — any @Async or CompletableFuture boundary clears the SecurityContext in a new thread. You must explicitly propagate the context across async boundaries.
For service-to-service authentication with a dedicated service account (not user JWT propagation), use a separate bean that maintains a cached service token with automatic refresh. The interceptor checks the token's expiry, refreshes it if needed, and adds the valid token. Use a ReentrantLock or AtomicReference for thread-safe token refresh.
For HMAC-signed requests (common for payment APIs like Stripe), the interceptor computes the signature from the request body and timestamp and adds it as a header. The body must be read from the RequestTemplate, the signature computed using HMAC-SHA256, and the result added as a header — all before the request is sent.
SecurityContextHolder.getContext() returns an empty context. Capture the Authentication before the async boundary and set it in a DelegatingSecurityContextRunnable wrapper, or use SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL) to propagate via InheritableThreadLocal.Resilience4j Fallback Factory and Circuit Breaker Integration
Spring Cloud OpenFeign integrates with Resilience4j via Spring Cloud CircuitBreaker when spring.cloud.openfeign.circuitbreaker.enabled=true. This enables the fallback and fallbackFactory attributes on @FeignClient. The critical difference between fallback and fallbackFactory is that fallbackFactory receives the exception that triggered the fallback, while fallback gets none — this makes fallbackFactory vastly more useful in production.
With FallbackFactory, your fallback implementation can make intelligent decisions: if the exception is a ResourceNotFoundException (404), re-throw it (no point in returning a fallback for 'order not found'). If it's a ServiceUnavailableException or ConnectTimeoutException, return cached data or a degraded response. If it's an AccessDeniedException, propagate it. This logic is impossible with a simple fallback class.
The circuit breaker name used by Feign is derived from the FeignClient interface name by default in Spring Cloud 2022.x. You can override it per-method using @CircuitBreaker annotations. Each circuit breaker has its own state machine and metrics, so a timeout on the payment service doesn't affect the circuit breaker for the inventory service.
Fallback implementations must be Spring beans — annotate them with @Component. The fallback for a FeignClient must implement the same interface. Each method returns the fallback value for that operation. Methods that cannot return a meaningful fallback (write operations that must succeed) should throw an exception from the fallback to propagate the failure to the caller.
Micrometer Metrics and Distributed Tracing with Feign
Spring Cloud OpenFeign automatically integrates with Micrometer to produce feign.client.requests metrics tagged with client name, method, host, and outcome (SUCCESS, CLIENT_ERROR, SERVER_ERROR). These metrics are invaluable for identifying slow or failing service integrations without looking at individual logs.
Key metrics to dashboard: feign.client.requests by outcome (track error rates per client), feign.client.requests percentile latency (P95, P99 per client), and feign.client.requests count (request rates). Alert on feign.client.requests{outcome='SERVER_ERROR'} exceeding a threshold for sustained periods.
Distributed tracing propagates automatically when Micrometer Tracing is on the classpath. The trace ID and span ID from the current request context are injected into outgoing Feign request headers (B3 headers: X-B3-TraceId, X-B3-SpanId, X-B3-Sampled, or W3C Trace-Context headers depending on configuration). The downstream service's Micrometer Tracing picks up these headers and continues the trace, linking the spans in Zipkin or Jaeger.
Custom span tags add business context to traces: add the order ID, user ID, or product ID to the span so traces are searchable by business identifiers in the trace UI. Use the Tracer bean to create custom spans within complex operations that span multiple Feign calls.
Feign Default Timeout Caused Cascading Thread Pool Exhaustion
- Never rely on Feign's default timeouts.
- Always configure explicit connect and read timeouts for every Feign client, sized to your SLA requirements.
- Add circuit breakers with slow-call detection to prevent slow dependencies from blocking your thread pool.
SecurityContextHolder.getContext().getAuthentication(); then in the async method, restore it with SecurityContextHolder.getContext().setAuthentication(auth).curl -s http://eureka-server:8761/eureka/apps/TARGET-SERVICE | python3 -m json.tool | grep -i statuscurl -v http://target-service:8080/actuator/health 2>&1 | grep '< HTTP'Key takeaways
Common mistakes to avoid
6 patternsUsing fallback instead of fallbackFactory
Not configuring explicit timeouts on Feign clients
Placing FeignConfig in the main component scan package
Calling Feign client from @Async method without propagating SecurityContext
Returning RetryableException from ErrorDecoder for POST/PUT requests
Not consuming the response body in ErrorDecoder
response.body().asReader(...)); the body must be consumed to release the HTTP connectionInterview Questions on This Topic
What is the difference between @FeignClient fallback and fallbackFactory?
Frequently Asked Questions
That's Spring Cloud. Mark it forged?
8 min read · try the examples if you haven't