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.

FeatureRestTemplateOpenFeign
Development StyleImperative (Manual URL/Body handling)Declarative (Interface-based)
MaintenanceHigh; changes in URI require manual updatesLow; mirrors the target Controller API
IntegrationManual configuration for Load BalancingNative integration with Spring Cloud LoadBalancer
ReadabilityLow; cluttered with technical HTTP logicHigh; 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, the url is typically omitted, and the name is 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.

LevelData LoggedUse Case
NONENo loggingProduction default (Best performance)
BASICRequest Method, URL, Status Code, Execution TimeGeneral production monitoring
HEADERSBasic info + Request/Response HeadersDebugging auth/header issues
FULLHeaders, Body, and MetadataLocal 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 @Configuration annotation 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

  1. 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.
  2. Merging:
    • If both global and client specific interceptors are defined via @Component and in application.yml, feign will combine them and both will be executed.
  3. Conflict Resolution:
    • We can set the spring.cloud.openfeign.client.default-to-properties=false property in application.yml define whether the Java Configuration to take priority over YAML (or vice versa).

Insight:
→ Use @Order annotation 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.

  1. Add the feign-okhttp dependency.
  2. 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.

FeatureStandard ImplementationWhy it matters
TimeoutsDefined in YAML (Connect & Read)Prevents thread exhaustion
RetriesConfigured via Resilience4jHandles transient network blips
SecurityRequestInterceptor for JWTEnsures end-to-end principal propagation
TransportApache HttpClient or OkHttpEnables connection reuse and performance
FallbacksDefined 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 traceId and spanId to the downstream service via HTTP headers (W3C standard by default).
  • Metrics:
    • It records timers and counters.
    • We can find these at the /actuator/metrics/http.client.requests endpoint.

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.

EndpointPurpose
/actuator/metrics/http.client.requestsView latency, throughput, and error rates for Feign calls.
/actuator/prometheusScrape metrics for Grafana dashboards.
/actuator/healthEnsure 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 should not be annotated with @Configuration if meant to apply it selectively, as a global @Configuration it 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

PropertyDescriptionDefault
spring.cloud.loadbalancer.enabledGlobally enable/disable load balancing.true
spring.cloud.loadbalancer.cache.ttlHow long to cache the instance list.35s
spring.cloud.loadbalancer.retry.enabledWhether 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.
  • Centralized Configuration:
    • Instead of hardcoding URLs in the @FeignClient annotation, use placeholders like url = "${exchange.service.url}" to manage environments via application.yml.
  • Error Handling:
    • Implement a FeignErrorDecoder to handle specific HTTP status codes (like 404 or 401) and map them to custom business exceptions.