OpenFeign Ultimate Guide
👉 Building resilient microservice communication with Spring Cloud OpenFeign.
👉 Advanced configuration: Timeouts, Interceptors, and ErrorDecoders.
What is OpenFeign ?
Spring Cloud OpenFeign is a declarative web service client that simplifies HTTP communication between microservices by using Java interfaces. It’s a Netflix-originated tool integrated into the Spring Cloud ecosystem to handle service-to-service communication.
It abstracts the complexity of manual HTTP request construction, allowing developers to define Interface (ie. “proxies”) for external services. By simply defining an interface with standard Spring MVC annotations, we can call another microservice as if it were a local bean.
Why use OpenFeign ?
In a distributed system, using traditional methods like RestTemplate requires repetitive code for URI construction, header management, and response deserialization.
OpenFeign help to eliminates these boilerplate codes, leading to cleaner, more maintainable codebases.
OpenFeign vs. RestTemplate
Below comparison demonstrates, why OpenFeign is the preferred choice for modern microservice architectures.
| Feature | RestTemplate | OpenFeign |
|---|---|---|
| Development Style | Imperative (Manual URL/Body handling) | Declarative (Interface-based) |
| Maintenance | High; changes in URI require manual updates | Low; mirrors the target Controller API |
| Integration | Manual configuration for Load Balancing | Native integration with Spring Cloud LoadBalancer |
| Readability | Low; cluttered with technical HTTP logic | High; looks like a standard Java interface |
OpenFeign implementation
Maven dependencies
To use OpenFeign, first step is to include the OpenFeign’s starter dependency in the pom.xml. This provides the necessary libraries for annotation processing and proxy generation.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Enable Feign clients
We must explicitly enable the Feign client’s scanning mechanism in the Spring Boot application class by annotating with @EnableFeignClients.
This enables Spring to scan for interfaces annotated with @FeignClient and create implementation beans for the Feign interfaces.
@SpringBootApplication
@EnableFeignClients // Scans for and creates implementation
public class CurrencyConversionApplication {
public static void main(String[] args) {
SpringApplication.run(CurrencyConversionApplication.class, args);
}
}
Define the Feign client interface
Now, create an interface which mirrors the endpoint of the target service. Where the name attribute identifies the service name, while the url(optional property) specifies the hardcoded base URL of the service.
Insight:
→ In production, theurlis typically omitted, and thenameis used by Service Discovery (like Eureka) to resolve the address dynamically.
@FeignClient(name = "currency-exchange", url = "localhost:8000")
public interface CurrencyExchangeService {
@GetMapping("/jpa/currency-exchange/from/{from}/to/{to}")
public CurrencyConversion retrieveExchangeRate(
@PathVariable String from,
@PathVariable String to
);
}
Consume remote service
Once the feign client interface is defined, injected it into any @Service or @RestController using standard Spring’s @Autowired annotation.
That’s all, the client is perfectly ready to connect with the remote service.
@RestController
public class CurrencyConversionController {
@Autowired
private CurrencyExchangeProxy proxy;
@GetMapping("/currency/from/{from}/to/{to}/quantity/{quantity}")
public CurrencyConversion calculateConversion(
@PathVariable String from, @PathVariable String to,
@PathVariable BigDecimal quantity) {
// Declarative call: Feels like a local method call
CurrencyConversion response = proxy.retrieveExchangeRate(from, to);
return new CurrencyConversion(
response.getId(), from, to, quantity,
response.getConversionMultiples(),
quantity.multiply(response.getConversionMultiples()),
response.getEnvironment()
);
}
}
Centralized configurations
For production grade apps, default settings are rarely sufficient. High-performance apps require custom timeouts, logging, and security headers.
We can set Feign client properties globally for all clients or specifically for a specific client using the name property value in the Spring-Boot’s application.yml (Recommended) or application.properties file.
YAML-based sample configuration:
spring:
cloud:
openfeign:
client:
config:
default: # Global settings
connectTimeout: 5000
readTimeout: 5000
loggerLevel: BASIC
currency-exchange: # Specific client override
connectTimeout: 2000
readTimeout: 2000
loggerLevel: FULL
Comprehensive logging
OpenFeign logging only responds to the DEBUG level. We must set the Spring’s logging level first and then define the Feign LoggerLevel.
Following are the logging levels supported by OpenFeign.
| Level | Data Logged | Use Case |
|---|---|---|
| NONE | No logging | Production default (Best performance) |
| BASIC | Request Method, URL, Status Code, Execution Time | General production monitoring |
| HEADERS | Basic info + Request/Response Headers | Debugging auth/header issues |
| FULL | Headers, Body, and Metadata | Local development/Detailed debugging |
Request Interceptors
To propagate security tokens (like JWT) from an incoming request to an outgoing Feign call, we need to provide implementation of RequestInterceptor.
In Spring Cloud OpenFeign, there are three main ways to register a RequestInterceptor. The method we choose depends on whether you want the interceptor to apply to all clients or just one specific client.
Insight:
→ This is the industry standard for microservice security.
Global registration (automatic)
By annotating the class with @Component Spring annotation, registering it as a Spring Bean.
Spring Cloud OpenFeign automatically detects the beans of type RequestInterceptor and chains all RequestInterceptor beans into a list and executes them sequentially before every request initiated by @FeignClient in the application.
This approach is Best suited for common functionality eg. Security tokens (JWT), Trace IDs, or headers required by all microservices.
@Component // Automatically registered for All Feign clients
public class FeignAuthInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
String token = TokenContext.getToken();
template.header("Authorization", "Bearer " + token);
}
}
Client specific registration (Java config)
If we want an interceptor to apply only to a specific service e.g. a third-party API that requires a unique API Key. This can be achieved by using the configuration attribute in the @FeignClient annotation.
We need to create a plain java class with method creating RequestInterceptor Bean and assign it to the client as demonstrated below.
Caution:
→ Do not use@Configurationannotation here, or it will become global config via Spring’s component scanning.
// Step 1: Create the config class (Plain Java class)
public class SpecificFeignConfig {
@Bean
public RequestInterceptor specificInterceptor() {
return template -> template
.header("X-Custom-Header", "SpecialValue");
}
}
// Step 2: Apply only to this client
@FeignClient(name = "currency-exchange",
configuration = SpecificFeignConfig.class)
public interface CurrencyExchangeService {
@GetMapping("/jpa/currency-exchange/from/{from}/to/{to}")
public CurrencyConversion retrieveExchangeRate(
@PathVariable String from,
@PathVariable String to
);
}
YAML configuration
This is the most flexible approach for production grade apps because decouples the config from code, allowing to enable/disable interceptors or change them per environment without modifying the code.
spring:
cloud:
openfeign:
client:
config:
# Apply to a specific client name
currency-exchange:
requestInterceptors:
- com.geekmonks.config.FeignAuthInterceptor
# Apply to all clients globally
default:
requestInterceptors:
- com.geekmonks.config.GlobalLogInterceptor
Interceptor lifecycle
The RequestInterceptor acts as the last “gatekeeper” before the HTTP request is handed over to the underlying client (like OkHttp or Apache HttpClient).
Execution Order & Priority
- Multiple Interceptors:
- If there are multiple interceptors (e.g., one for Auth and one for Logging), Feign will execute them in the order they are registered as beans.
- Merging:
- If both global and client specific interceptors are defined via
@Componentand inapplication.yml, feign will combine them and both will be executed.
- If both global and client specific interceptors are defined via
- Conflict Resolution:
- We can set the
spring.cloud.openfeign.client.default-to-properties=falseproperty in application.yml define whether the Java Configuration to take priority over YAML (or vice versa).
- We can set the
Insight:
→ Use@Orderannotation to define order of execution like@Order(1),@Order(2)etc.
Industry Best Practices & Resilience
Architecting a resilient system requires handling failures gracefully and optimizing the underlying transport layer.
1. Error handling
Instead of wrapping every Feign call in a try-catch, use an ErrorDecoder. This allows you to catch 4xx/5xx errors globally and map them to custom business exceptions.
public class MyErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
return switch (response.status()) {
case 404 -> new ResourceNotFoundException(
"Service endpoint not found");
case 401 -> new UnauthorizedException(
"Invalid credentials for service call");
default -> new Exception("Generic Service Error");
};
}
}
2. Connection pooling
By default, Feign uses HttpURLConnection, which does not support connection pooling. For high-traffic production apps, always switch to Apache HttpClient or OkHttp.
Follow below steps to switch to OkHttp.
- Add the feign-okhttp dependency.
- Set spring.cloud.openfeign.okhttp.enabled=true in application config.
3. Resilience and Retries
Modern Spring Cloud uses Resilience4j for circuit breaking. If the target service is down, Feign can trigger a “fallback” method to return cached or default data, preventing a cascading failure.
Production Checklist summary
This table provides a quick reference for auditing your Feign implementation before deployment.
| Feature | Standard Implementation | Why it matters |
|---|---|---|
| Timeouts | Defined in YAML (Connect & Read) | Prevents thread exhaustion |
| Retries | Configured via Resilience4j | Handles transient network blips |
| Security | RequestInterceptor for JWT | Ensures end-to-end principal propagation |
| Transport | Apache HttpClient or OkHttp | Enables connection reuse and performance |
| Fallbacks | Defined via @FeignClient(fallback=...) | Provides graceful degradation |
Observability in Spring Cloud OpenFeign
In Spring Boot 3 and later versions, observability has moved from Spring Cloud Sleuth to Micrometer Observation. This shift provides a unified API for both metrics (Micrometer) and tracing (Micrometer Tracing).
Spring Cloud OpenFeign provides built-in support for observability via Micrometer. When enabled, it automatically instruments your Feign clients to produce metrics and distributed tracing spans for every outgoing HTTP request.
1. Required Dependencies
To enable observability, we need the feign-micrometer bridge and a tracing implementation (like Brave or OpenTelemetry).
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
2. Enabling Observability
Observability is generally enabled by default if the dependencies are present, but you can explicitly control it in your application.yml:
spring:
cloud:
openfeign:
micrometer:
enabled: true # Global toggle
client:
config:
currency-exchange: # Specific client name
micrometer:
enabled: true
3. How it works ?
When feign-micrometer is on the classpath, Spring Cloud registers a MicrometerObservationCapability. This capability wraps the Feign client components to start an Observation whenever a request is made.
- Tracing:
- It automatically propagates the
traceIdandspanIdto the downstream service via HTTP headers (W3C standard by default).
- It automatically propagates the
- Metrics:
- It records timers and counters.
- We can find these at the
/actuator/metrics/http.client.requestsendpoint.
4. Customizing observations
If we need to add custom tags (key-value pairs) to your Feign client spans or metrics, we can provide an FeignClientObservationConvention.
@Configuration
public class FeignConfig {
@Bean
public FeignClientObservationConvention customConvention() {
return new DefaultFeignClientObservationConvention() {
@Override
public KeyValues getLowCardinalityKeyValues(
FeignContext context) {
return super.getLowCardinalityKeyValues(context)
.and("custom.tag", "my-value");
}
};
}
}
5. Monitoring and verification
Once configured, you can verify the observability data through these Actuator endpoints.
| Endpoint | Purpose |
|---|---|
/actuator/metrics/http.client.requests | View latency, throughput, and error rates for Feign calls. |
/actuator/prometheus | Scrape metrics for Grafana dashboards. |
/actuator/health | Ensure the tracing backend (Zipkin/OTLP) is reachable. |
Insight:
→ Log Correlation: If we use the default Spring Boot log pattern, we will now see[service-name, traceId, spanId]in your console logs automatically whenever a Feign client call is in progress.
Load Balancing
When @FeignClient annotation is configured with service-name instead of a hardcoded URL, Feign automatically looks for a load balancer to resolve that name into a physical IP address and port.
1. Maven dependency
Ensure the LoadBalancer starter dependency is present in pom.xml. When using spring-cloud-starter-openfeign, this is often included transitively, but it is safer to declare it explicitly.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
2. Service Discovery Integration
The easiest way to handle multiple instances is by using a Service Registry (like Eureka or Consul). LoadBalancer will automatically fetch the list of available instances from the registry.
The Client Interface:
@FeignClient(name = "currency-exchange")
public interface CurrencyExchangeService {
@GetMapping("/jpa/currency-exchange/from/{from}/to/{to}")
public CurrencyConversion retrieveExchangeRate(
@PathVariable String from,
@PathVariable String to
);
}
3. Static Instance List (Manual Configuration)
If we aren’t using a service registry, then the instances for a service can be manually defined in apps application.yml.
spring:
cloud:
loadbalancer:
clients:
currency-exchange:
# Define manual instances if not using Eureka/Consul
instances:
- uri: http://service-host:8081
- uri: http://service-host:8082
4. Customizing the balancing algorithm
By default, Spring Cloud LoadBalancer uses a Round Robin strategy. To change this (e.g., to Random), you need to create a configuration class.
Critical:
This configuration class shouldnotbe annotated with@Configurationif meant to apply it selectively, as a global@Configurationit will affect all clients.
public class CustomLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
Environment env, LoadBalancerClientFactory factory) {
String name = env.getProperty(
LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(factory.getLazyProvider(name,
ServiceInstanceListSupplier.class), name);
}
}
Apply to specific client:
@FeignClient(name = "currency-exchange",
configuration = CustomLoadBalancerConfig.class)
public interface CurrencyExchangeClient { ... }
5. Essential properties
| Property | Description | Default |
|---|---|---|
spring.cloud.loadbalancer.enabled | Globally enable/disable load balancing. | true |
spring.cloud.loadbalancer.cache.ttl | How long to cache the instance list. | 35s |
spring.cloud.loadbalancer.retry.enabled | Whether to retry on a different instance if one fails. | false |
Best practices
- Shared DTOs:
- Ensure the response bean (
CurrencyConversion) in the client service has matching field names with the producer service to allow Jackson to deserialize data correctly.
- Ensure the response bean (
- Centralized Configuration:
- Instead of hardcoding URLs in the
@FeignClientannotation, use placeholders likeurl = "${exchange.service.url}"to manage environments viaapplication.yml.
- Instead of hardcoding URLs in the
- Error Handling:
- Implement a
FeignErrorDecoderto handle specific HTTP status codes (like 404 or 401) and map them to custom business exceptions.
- Implement a