In this tutorial, we get an introduction to Feign. We are also going to talk a little bit about Spring Boot and Feign.
Introduction
In this tutorial, we will see how to write declarative REST client using Feign. It makes writing web clients easy by abstracting away the boilerplate code we typically write. All we need to do is declare an interface and annotate it and its methods. The actual implementation will be done at runtime by the framework along with various message conversion between the calls. We first need to set up a sample REST API running with known endpoints so that we can call these using feign clients.
1. Service Setup
The example service is a simple spring-boot app containing a single REST controller having a couple of endpoints:
@RestController
public class ProductController {
private static List < Product > productList = new ArrayList < > ();
static {
productList.add(new Product(1, "product-1", 12.0));
productList.add(new Product(2, "product-2", 34.0));
productList.add(new Product(3, "product-3", 9.0));
}
@GetMapping("/products")
public ResponseEntity << ? > getProsucts() {
return ResponseEntity.ok(productList);
}
@GetMapping("/product/{id}")
public ResponseEntity << ? > getProsucts(@PathVariable int id) {
Product product = findProduct(id);
if (product == null) {
return ResponseEntity.badRequest()
.body("Invalid product Id");
}
return ResponseEntity.ok(product);
}
private Product findProduct(int id) {
return productList.stream()
.filter(user -> user.getId()
.equals(id))
.findFirst()
.orElse(null);
}
}
The two endpoints are ‘/products
’ and ‘product/{id}
’ that returns a list of products and a product based on the id passed respectively. If the product is not found it returns an HTTP.BAD_REQUEST
response. Following is the application.properties
:
server.port=8081
spring.application.name=product-service
So product-service will be running on port 8081
2. Feign Client Setup
The best way to create a spring boot application is Spring Initializr. Select your Spring Boot version, and add the “Web”, “Feign” dependency. Generate it as a Maven project and you’re all set. Notice the following dependency in pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Feign was initially sponsored by Netflix but later open sourced. In spring-boot 1.x version feign dependencies was from Netflix but stating from spring-boot 2.x openfeign is used. Enable the project to use feign clients by adding ‘@EnableFeignClients
’ in your main class:
@SpringBootApplication
@EnableFeignClients
public class FeignClientExampleApplication {
public static void main(String[] args) {
SpringApplication.run(FeignClientExampleApplication.class, args);
}
}
Let’s define our product-service feign client. First, we have to make an interface ProductServiceClient and annotate it with @FeignClient
specifying a name and the URL of product service is running. As for methods we just need to declare it and annotate just like Spring MVC style. We also need to tell what input it might require and response type it needs to convert.
@FeignClient(name = "product-service", url = "http://localhost:8081")
public interface ProductServiceClient {
@RequestMapping(value = "/products", method = RequestMethod.GET)
public List < Product > getAllProducts();
@RequestMapping(value = "/product/{id}", method = RequestMethod.GET)
public Product getProduct(@PathVariable("id") int productId);
}
Spring will make out the implementation of this interface at runtime using openfeign. We have to define the Product class in the project as we are converting the response to it:
public class Product {
private Integer id;
private String name;
private double price;
public Product(Integer id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
Now let’s use this ProductServiceClient in our AppController. For that, we need to @Autowired
the ProductServiceClient into our controller.
@RestController
public class AppController {
@Autowired
ProductServiceClient productServiceClient;
@GetMapping("/fetchProducts")
public ResponseEntity << ? > fetchProducts() {
return ResponseEntity.ok(productServiceClient.getAllProducts());
}
@GetMapping("/fetchProduct/{id}")
public ResponseEntity << ? > fetchProduct(@PathVariable int id) {
return ResponseEntity.ok(productServiceClient.getProduct(id));
}
}
That’s all we have to do. Let’s test using Postman:
Behind the scenes, all the boilerplate code is handled by spring along with openfeign libraries. This results in less code and less chance to make mistakes.
4. Handling Errors with Feign
By default Feign only throws FeignException for any error situation (where the response is other than 2XX or if there is a conversion error etc).
But you might want to capture these error and handle the response on your end like the BAD_REQUEST error that is thrown by the product-service if the product id is not found. Let’s first define our custom ProductNotFound Exception:
public class ProductNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1 L;
public ProductNotFoundException(String msg) {
super(msg);
}
}
Now Let’s define our exception handler for this app:
@RestControllerAdvice
public class AppExceptionHandler {
@ResponseBody
@ExceptionHandler(value = ProductNotFoundException.class)
public ResponseEntity << ? > handleException(ProductNotFoundException exception) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(exception.getMessage());
}
}
Now to capture FeignException and provide your own implementation you need to implement feign.codec.ErrorDecoder and register it as a bean in a Spring application context.
@Component
public class AppFeignErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
@Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
throw new ProductNotFoundException("Product Not Found");
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
As you can see, we captured all the 4xx error and throw our own custom exception.
5. Using Feign with Eureka and Ribbon
Typically in a microservice architecture, all of your services are registered to a registry service like Eureka and it could be that there are multiple instances of the same service running. Hence you might not want to hardcode a URL in your Feign clients and also wants to connect to that service instance that response quicker.
5.1. Eureka Server Setup
Let’s set up a Eureka server. Again, we will use Spring Initializr to create it.
Now all you need to do is add @EnableEurekaServer
to the main class:
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
Following is the application.properties
:server.port=8761
.So our Eureka server will be running on 8761 which is the recommended port for it by spring.
5.2. Eureka Server Setup
Now to register our 2 services to it. You just need to add the following dependencies in your pom.xml
:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
Now add @EnableDiscoveryClient
to the main class for both the application like :
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignClientExampleApplication {
public static void main(String[] args) {
SpringApplication.run(FeignClientExampleApplication.class, args);
}
}
Start both apps and you will see them registering themselves in Eureka:
Now let’s remove the hardcoded URL from the ProductServiceClient:
@FeignClient(name = "product-service" /*, url = "http://localhost:8081"*/ )
public interface ProductServiceClient {
// same as previous
}
That’s it. Now behind the scenes, ProductServiceClient will fetch the URL from Eureka server using the name property (“product-service”)
. Ribbon is a client-side load balancer that comes along with the dependency that we included. This will automatically fetch the best server for us to make the rest call.
Summary
In this article, we covered how to use Feign for making REST calls declaratively. We then saw how to handle errors scenarios. At last, we saw how to use it in a microservice architecture. The sample code for this article can be found on Github.
hi,
how to add the multiple headers in feign client
like
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(actuatorUserName, ActuatorPswd);
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity entity = new HttpEntity(headers);
ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
Already I tried in the infterface
@HeaderMap Map headers,
and
@HeaderMap MultivalueMap headers,
but it not working
please let me know
Did you tried something like RequestInterceptor?