Spring Cloud OpenFeign Configuration and Advanced Usage Guide

Overview

OpenFeign is a declarative HTTP client for Java that simplifies service-to-service communication in distributed systems. Spring Cloud extends Feign with auto-configuration, integration with Spring MVC annotations, load balancing via Spring Cloud LoadBalancer, circuit breaking, and observability support.

GitHub: https://github.com/spring-cloud/spring-cloud-openfeign
Official Docs: https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/

Integration with Spring Boot

1. Add the Starter Dependency

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

Ensure version compatibility between Spring Boot and Spring Cloud — refer to the official compatibility matrix.

2. Enable Feign Clients

Annotate your main application class:

@SpringBootApplication
@EnableFeignClients
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
    }
}

3. Define a Feign Client Interface

@FeignClient(name = "inventory-service", path = "/api/inventory")
public interface InventoryClient {

    @GetMapping("/items/{id}")
    Item getItem(@PathVariable Long id);

    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
    Item createItem(@RequestBody Item item);

    @DeleteMapping("/items/{id}")
    void deleteItem(@PathVariable Long id);
}
  • name: Logical service identifier (used for service discovery if Eureka or LoadBalancer is present).
  • url: Optional absolute URL (bypasses discovery).
  • path: Base path prefix applied to all method-level mappings.

Customizing Client Behavior

Override Default Beans Per Client

Each @FeignClient gets its own Spring context. To customize components for a specific client, define a configuration class and reference it:

@FeignClient(
    name = "inventory-service",
    configuration = InventoryClientConfig.class
)
public interface InventoryClient { /* ... */ }

@Configuration
public class InventoryClientConfig {
    @Bean
    public Encoder feignEncoder() {
        return new JacksonEncoder();
    }

    @Bean
    public RequestInterceptor authInterceptor() {
        return template -> template.header("Authorization", "Bearer token");
    }
}

⚠️ Avoid @Configuration on shared config classes unless intended globally — use @Configuration only when explicitly needed and ensure proper scoping.

Configure via Properties

Apply settings per client or globally using application.yml:

feign:
  client:
    config:
      default:
        connectTimeout: 3000
        readTimeout: 6000
        loggerLevel: BASIC
      inventory-service:
        connectTimeout: 5000
        decode404: true

To apply globally, use default as the feignName. Property-based config takes precedence over Java-config unless feign.client.default-to-properties=false is set.

Multiple Clients with Same Name

Use contextId to isolate bean definitions:

@FeignClient(contextId = "adminInventory", name = "inventory-service", configuration = AdminConfig.class)
public interface AdminInventoryClient { /* ... */ }

@FeignClient(contextId = "publicInventory", name = "inventory-service", configuration = PublicConfig.class)
public interface PublicInventoryClient { /* ... */ }

Disable Parent Context Inheritance

Prevent a client from inheriting beans from the parent ApplicationContext:

@Bean
public FeignClientConfigurer feignClientConfigurer() {
    return () -> false;
}

Timeouts and Retries

Timeout Configuration

Three primary approaches:

  1. Declarative (YAML):

    feign:
      client:
        config:
          default:
            connectTimeout: 2000
            readTimeout: 8000
    
  2. Per-client via Request.Options:

    @Bean
    public Request.Options customOptions() {
        return new Request.Options(2_000, 8_000);
    }
    
  3. Per-request via method parameter:

    @GetMapping("/status")
    Status getStatus(Request.Options options);
    
    // Call site
    client.getStatus(new Request.Options(1_500, 5_000));
    

Retry Strategy

Feign uses Retryer to control retry behavior. By default, Retryer.NEVER_RETRY disables retries.

Custom retry logic:

@Bean
public Retryer retryer() {
    // maxAttempts=3, initialInterval=100ms, maxInterval=1000ms
    return new Retryer.Default(100, 1000, 3);
}

For fine-grained control, implement Retryer:

public class AdaptiveRetryer implements Retryer {
    private final int maxRetries;
    private int attempt;

    public AdaptiveRetryer(int maxRetries) {
        this.maxRetries = maxRetries;
        this.attempt = 0;
    }

    @Override
    public void continueOrPropagate(RetryableException e) {
        if (++attempt > maxRetries) throw e;
        try {
            Thread.sleep((long) Math.pow(2, attempt) * 100); // exponential backoff
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
            throw e;
        }
    }

    @Override
    public Retryer clone() {
        return new AdaptiveRetryer(maxRetries);
    }
}

Logging

Feign logs are tied to SLF4J categories matching the client’s fully qualified interface name. Enable debug logging to see request/response details:

logging:
  level:
    com.example.InventoryClient: DEBUG

Log levels:

  • NONE: No logging.
  • BASIC: Method, URL, status, duration.
  • HEADERS: Includes headers.
  • FULL: Full body and metadata.

Set level programmatically:

@Bean
Logger.Level feignLoggerLevel() {
    return Logger.Level.FULL;
}

Error Handling and Fallbacks

When integrated with Spring Cloud CircuitBreaker, fallbacks execute on circuit open or call failure.

Static Fallback Class

@FeignClient(
    name = "inventory-service",
    fallback = InventoryFallback.class
)
public interface InventoryClient { /* ... */ }

@Component
public class InventoryFallback implements InventoryClient {
    @Override
    public Item getItem(Long id) {
        return new Item("fallback-item", 0);
    }
}

Fallback Factory (with exception context)

@FeignClient(
    name = "inventory-service",
    fallbackFactory = InventoryFallbackFactory.class
)
public interface InventoryClient { /* ... */ }

@Component
public class InventoryFallbackFactory implements FallbackFactory<InventoryClient> {
    @Override
    public InventoryClient create(Throwable cause) {
        return new InventoryClient() {
            @Override
            public Item getItem(Long id) {
                log.warn("Failed to fetch item {} due to {}", id, cause.getMessage());
                return new Item("recovered-item", -1);
            }
        };
    }
}

Observability and Metrics

Micrometer metrics are auto-enabled if feign-micrometer is on the classpath and MeterRegistry is available.

Enable globally:

spring:
  cloud:
    openfeign:
      micrometer:
        enabled: true

Or per client:

feign:
  client:
    config:
      inventory-service:
        micrometer:
          enabled: true

Customize observation registry:

@Bean
public MicrometerObservationCapability observationCapability(ObservationRegistry registry) {
    return new MicrometerObservationCapability(registry);
}

Compression Support

Enable request/response GZIP compression:

spring:
  cloud:
    openfeign:
      compression:
        request:
          enabled: true
          mime-types: text/xml,application/xml,application/json
          min-request-size: 2048
        response:
          enabled: true

⚠️ Compression is disabled when OkHttpClient is active and Content-Encoding or Accept-Encoding headers are present.

Query and Path Parameter Binding

  • Use @RequestParam for query parameters.
  • Use @PathVariable for URI path segments.
  • For complex objects as query maps, use @SpringQueryMap:
public class SearchCriteria {
    private String category;
    private Integer limit;
}

@GetMapping("/search")
List<Product> search(@SpringQueryMap SearchCriteria criteria);

HATEOAS and Matrix Variables

If spring-boot-starter-hateoas or spring-boot-starter-data-rest is present, Feign supports marshalling EntityModel, CollectionModel, and PagedModel.

Matrix variables are supported via @MatrixVariable, requiring explicit path variable alignment:

@GetMapping("/objects/{matrixVars}")
Map<String, List<String>> getObjects(@MatrixVariable Map<String, List<String>> vars);

Collection Format Handling

Specify serializasion format for collection parameters:

@FeignClient(name = "catalog")
public interface CatalogClient {
    @CollectionFormat(feign.CollectionFormat.CSV)
    @GetMapping("/products")
    List<Product> findProducts(@RequestParam List<String> ids);
}

Manual Client Construction

For full control, build clients imperatively:

@Autowired
private Decoder decoder;

@Autowired
private Encoder encoder;

@Bean
public CatalogClient catalogClient() {
    return Feign.builder()
        .decoder(decoder)
        .encoder(encoder)
        .contract(new SpringMvcContract())
        .requestInterceptor(new AuthInterceptor())
        .target(CatalogClient.class, "https://catalog-api.example.com");
}

Refreshable Clients

Enable dynamic property refresh for timeouts and URLs:

spring:
  cloud:
    openfeign:
      client:
        refresh-enabled: true

Then update properties at runtime via /actuator/refresh.

Caching Support

With @EnableCaching, Feign respects @Cacheable, @CacheEvict, etc., provided spring-cloud-openfeign.cache.enabled=true (default). Disable with:

spring:
  cloud:
    openfeign:
      cache:
        enabled: false

Reactive Support

OpenFeign does not natively support reactive types (Mono, Flux). WebClient remains the preferred choice for reactive service calls.

Data Conversion Extensions

Spring Data Page and Sort converters are auto-registered if spring-data-commons and jackson-databind are present. Disable with:

spring:
  cloud:
    openfeign:
      autoconfiguration:
        jackson:
          enabled: false

URL Resolution Strategies

Scenario Example Behavior
url in annotation @FeignClient(url="http://localhost:8080") Direct HTTP call; no load balancing
url in properties only spring.cloud.openfeign.client.config.myclient.url=http://host:port Resolved from config; refreshable if enabled
name only @FeignClient(name="my-service") Uses service discovery + load balancing
Both name and url Annotation + property url from annotation wins; property ignored

Parameter Mapping Examples

Server Endpoint Feign Method Signature
GET /users/{id} @GetMapping("/users/{id}") User get(@PathVariable Long id)
GET /search?q=foo @GetMapping("/search") List<R> search(@RequestParam String q)
POST /users with JSON body @PostMapping("/users") User create(@RequestBody User user)
GET /items?ids=1&ids=2 @GetMapping("/items") List<I> list(@RequestParam List<Long> ids)
GET /items/{id}/details @GetMapping("/{id}/details") Detail get(@PathVariable Long id)

Troubleshooting Initialization Errors

If lazy initialization is required (e.g., during startup), inject clients via ObjectProvider:

@Autowired
private ObjectProvider<InventoryClient> inventoryClientProvider;

// Later...
InventoryClient client = inventoryClientProvider.getObject();

Disabling Hystrix (Legacy Note)

Hystrix support is deprecated. Prefer Spring Cloud CircuitBreaker with Resilience4j or Sentinel. If still used, disable via:

feign:
  hystrix:
    enabled: false

Summary of Key Configuration Properties

Property Description Default
feign.client.config.default.connectTimeout Connection timeout (ms) 10000
feign.client.config.default.readTimeout Read timeout (ms) 60000
feign.client.config.default.loggerLevel Logging verbosity NONE
feign.client.refresh-enabled Enable dynamic refresh false
spring.cloud.openfeign.micrometer.enabled Micrometer metrics true
spring.cloud.openfeign.cache.enabled Cache annotation support true
spring.cloud.openfeign.compression.request.enabled Request compression false
spring.cloud.openfeign.compression.response.enabled Response compression false

Tags: spring-cloud openfeign microservices http-client spring-boot

Posted on Sat, 10 Oct 2026 16:31:05 +0000 by kenshintomoe225