Careeroza
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
| Client | Style | Notes |
|---|---|---|
RestClient | Fluent, synchronous | Spring 6.1+. Full control; used in the basic level |
WebClient | Reactive | For WebFlux apps and the gateway |
| OpenFeign | Declarative interface | Very common in existing Spring Cloud code; now "feature complete" (maintenance mode) |
HTTP Interface (@HttpExchange) | Declarative interface | Built 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);
}
}