OpenFeign Basics

An Interface Instead of HTTP Code

With OpenFeign you write a Java interface with Spring MVC annotations, and Spring creates the HTTP client for you at runtime. Load balancing through Eureka is automatic: the name is the service ID.

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@SpringBootApplication
@EnableFeignClients                    // scan for @FeignClient interfaces
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

public record ProductDto(Long id, String name, BigDecimal price, int stock, String category) {}
public record StockRequest(int quantity) {}

@FeignClient(name = "product-service", path = "/api/products")
public interface ProductClient {

    @GetMapping("/{id}")
    ProductDto getProduct(@PathVariable("id") Long id);

    @GetMapping
    List<ProductDto> byCategory(@RequestParam("category") String category,
                                @RequestParam(value = "page", defaultValue = "0") int page);

    @PostMapping("/{id}/reserve")
    void reserveStock(@PathVariable("id") Long id, @RequestBody StockRequest request);
}

Using the Client

@Service
@RequiredArgsConstructor
class CartPricingService {

    private final ProductClient productClient;   // injected like any bean

    BigDecimal total(List<CartLine> lines) {
        return lines.stream()
                .map(l -> productClient.getProduct(l.productId()).price()
                        .multiply(BigDecimal.valueOf(l.quantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}

Example

With logger-level: basic (see the timeouts topic) each call is logged:

curl -X POST localhost:8082/api/orders/price -H "Content-Type: application/json" -d '[{"productId":7,"quantity":2},{"productId":9,"quantity":1}]'

Example output

{"total":3797.00}

DEBUG c.s.order.client.ProductClient : [ProductClient#getProduct] ---> GET http://product-service/api/products/7 HTTP/1.1
DEBUG c.s.order.client.ProductClient : [ProductClient#getProduct] <--- HTTP/1.1 200 (14ms)
DEBUG c.s.order.client.ProductClient : [ProductClient#getProduct] ---> GET http://product-service/api/products/9 HTTP/1.1
DEBUG c.s.order.client.ProductClient : [ProductClient#getProduct] <--- HTTP/1.1 200 (9ms)

Note: Always give @PathVariable("id") and @RequestParam("category") an explicit name in Feign interfaces. It avoids errors when the code is compiled without -parameters.

Choosing an HTTP Client

ClientStyleNotes
RestClientFluent, synchronousSpring 6.1+. Full control; used in the basic level
WebClientReactiveFor WebFlux apps and the gateway
OpenFeignDeclarative interfaceVery common in existing Spring Cloud code; now "feature complete" (maintenance mode)
HTTP Interface (@HttpExchange)Declarative interfaceBuilt into Spring 6; the recommended choice for new code
// The Spring 6 way: same idea as Feign, no extra library
@HttpExchange("/api/products")
public interface ProductHttpClient {
    @GetExchange("/{id}")
    ProductDto getProduct(@PathVariable("id") Long id);
}

@Configuration
class HttpInterfaceConfig {
    @Bean
    ProductHttpClient productHttpClient(@LoadBalanced RestClient.Builder builder) {
        RestClient restClient = builder.baseUrl("http://product-service").build();
        return HttpServiceProxyFactory
                .builderFor(RestClientAdapter.create(restClient))
                .build()
                .createClient(ProductHttpClient.class);
    }
}