Spring Cloud OpenFeign an openfeign integration module for spring boot. Feign is one of the best HTTP clients which we could use with Spring boot to communicate with third-party REST APIs. In this tutorial, we are going to explain how we can configure feign client inside a spring boot app to consume third party REST API.

Additionally, we are going to configure the same feign client in order to support real-world scenarios in development.

Major Topics inside this article, You can just click on the area you looking for if you need exact answer.

Advantage of Using Feign as an HTTP Client

As I discovered the main advantage in using feign for an HTTP client is that all we need to do is write an interface with pre-defined annotations and feign automatically do the stuff that needs to happen inside a REST client.

Let’s start coding,

Let’s create a fresh spring boot application using spring initializr, If you are not familiar with creating a spring boot application just use our guide on HOW TO CREATE A SPRING BOOT PROJECT. Here I’m using Lombok plugin to keep the code simple and clean. If you need to learn how we can use lombok in spring boot follow our article Guide to use Lombok In Spring Boot.

Adding Required Dependencies

In order to integrate Feign Client we need to include ‘spring-cloud-starter-openfeign’ along with ‘spring-cloud-dependencies’ into our project. In this tutorial, I’m using Gradle as a project building tool.

To do that add following dependencies into build.gradle,

implementation 'org.springframework.cloud:spring-cloud-dependencies:Hoxton.RELEASE'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:2.2.5.RELEASE'

Configuring Feign Client

All right, now we are ready to configure feign client inside this project. So let’s start coding to consume third party API.

Here we are using the third party fake API with pagination to consume using feign client. This API is hosted and open to consume for free. There are many API endpoints that cover all the HTTP methods.

First, we need to enable feign client inside the application by using ‘@EnableFeignClients’ annotation in the main class.

package com.javatodev.feign;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class OpenFeignIntegrationApplication {

    public static void main(String[] args) {
        SpringApplication.run(OpenFeignIntegrationApplication.class, args);
    }

}

then we can start creating the interface which will act as the client for this API. To do that we just need to create an interface inside the project.

package com.javatodev.feign.client;

import com.javatodev.feign.rest.response.Airline;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.List;

@FeignClient(value = "instantwebtools-api", url = "https://api.instantwebtools.net/v1/")
public interface ApiClient {

    @RequestMapping(method = RequestMethod.GET, value = "/airlines")
    List<Airline> readAirLines();

    @RequestMapping(method = RequestMethod.GET, value = "/airlines/{airlineId}")
    Airline readAirLineById(@PathVariable("airlineId") String airlineId);

}

In this example, we are going to consume the following API endpoints and it returns Airline Response.

https://api.instantwebtools.net/v1/airlines
https://api.instantwebtools.net/v1/airlines/:id

Response class for the API.

package com.javatodev.feign.rest.response;

import lombok.Data;

@Data
public class Airline {
    private Long id;
    private String name;
    private String country;
    private String logo;
    private String slogan;
    private String headQuaters;
    private String website;
    private String established;
}

Here we should set value which is the identifier for this API client and URL which is the base URL to the 3rd party API we are going to consume.

Send Requests Using Feign Client

All right now we are ready to consume this API through our application.

For the moment I’ll call the API with a simple Rest Controller. But in real world application these APIs could be called from a service or repository or anywhere you preferred inside a Spring Boot project.

package com.javatodev.feign.controller;

import com.javatodev.feign.client.ApiClient;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping(value = "/airlines")
@RequiredArgsConstructor
public class AirlineController {

    private final ApiClient apiClient;

    @GetMapping
    public ResponseEntity readAirlineData (@RequestParam(required = false) String airlineId) {
        if (airlineId == null) {
            return ResponseEntity.ok(apiClient.readAirLines());
        }
        return ResponseEntity.ok(apiClient.readAirLineById(airlineId));
    }

}

Testing With Postman

Testing Feign Client in Spring Boot With Postman
Testing with Postman

Loading Feign Configurations From Application Properties

There was few hard coded values which we have used in ApiClient. We can load those configurations from application.properties since those are constants in many cases and it will be easy to change whenever needed. To do that just need to add key value pair into the propeties and we could use those inside ApiClient as below.

app.feign.config.name=instantwebtools-api
app.feign.config.url=https://api.instantwebtools.net/v1/

Then we need to add these keys into the ApiClient in order to capture values from properties.

package com.javatodev.feign.client;

import com.javatodev.feign.rest.response.Airline;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.List;

@FeignClient(value = "${app.feign.config.name}", url = "${app.feign.config.url}")
public interface ApiClient {

    @RequestMapping(method = RequestMethod.GET, value = "/airlines")
    List<Airline> readAirLines();

    @RequestMapping(method = RequestMethod.GET, value = "/airlines/{airlineId}")
    Airline readAirLineById(@PathVariable("airlineId") String airlineId);

}

Custom Configurations For Feign Client in Spring Boot

Feign support custom clients instead of default client. Eg;- OkHttp client which allows using HTTP/2. Additionally, there are multiple clients that support feign client in Spring boot to add more value additions to the feign.

Here we are going to create an additional class with @configuration to achieve overriding to default client with OKHTTP client.

package com.javatodev.feign.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import feign.Logger;
import feign.okhttp.OkHttpClient;

@Configuration
public class CustomFeignClientConfiguration {
    
    @Bean
    public OkHttpClient client() {
        return new OkHttpClient();
    }

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

Then we should add these configurations into the feign client we developed by using configuration in @FeignClient

@FeignClient(value = "${app.feign.config.name}", url = "${app.feign.config.url}", configuration = CustomFeignClientConfiguration.class)

Setting Dynamic Headers into the Feign Client

Here I’m explaining ways to set HTTP headers to feign client. This is since in some cases we need to set dynamic headers like authentication tokens, basic auth credentials, auth identification, content type and etc, to a request we are sending to a 3rd party API.

Setting headers on top of the request

This is the most common and easiest way to set header into the feign client.

@RequestMapping(method = RequestMethod.GET, value = "/airlines")
    List<Airline> readAirLines(@RequestHeader("X-Auth-Token") String token);

But if we use this pattern we should add same into the every API endpoint where it should be applicable and we need to pass every header while sending a request.

Setting global headers to the feign client

Let’s assume our 3rd party API requires the following headers to be present with every request which is coming to the resources.

  • Content-Type:application/json
  • Accept:application/json
  • X-Auth-Code: 920d4d0b85443d98d86cb3c8c81d9eed

We can achieve this using feign.RequestInterceptor. Basically, this adds header values to every and each request going through the feign client in Spring boot. There are two ways of configuring RequestInterceptor with a spring boot application.

In here we can use the same configuration class which we written for above step to add requestinterceptor to set dynamic headers into every and each request foing through feign client in spring boot.

We just need to add below code snippet into our configuration class.

@Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> {
            requestTemplate.header("Accept", "application/json");
            requestTemplate.header("Content-Type", "application/json");
            requestTemplate.header("X-Auth-Code", "920d4d0b85443d98d86cb3c8c81d9eed");
        };
    }

Here in this example I’ve only used hard coded value for X-Auth-Code, But if you need to add dynamic authentication credentials or a tokens, just add it here.

Setting Feign Configurations Using Application Properties

We can define feign client properties using application.properties or yaml as well. This is since there is no need of having some configurations inside java based configurations instead of application properties. Eg:- connection time out value.

Let’s see how we can set those values using application.properties

feign.client.config.default.connect-timeout=20000
feign.client.config.default.read-timeout=20000

Here we have set global configuration for every and each feign client defined inside this spring boot project. But what if you need to set different configurations to different clients? It’s possible with feign client too.

To do that you just need to do is adding the feign client name instead of default to the configuration.

feign.client.config.instantwebtools-api.connect-timeout=20000
feign.client.config.instantwebtools-api.read-timeout=20000

Configure Error Handling For Feign Client in Spring Boot

In this case feign give us feign.codec.ErrorDecoder to capture and handle errors inside feign client. Basically you just need to write error CustomErrorDecoder which decodes any type of error happens inside feign communication.

package com.javatodev.feign.config;

import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class FeignCustomErrorDecoder implements ErrorDecoder {

    @Override public Exception decode(String methodKey, Response response) {

        switch (response.status()) {
            case 400:
                log.error("Error in request went through feign client");
                //handle exception
                return new Exception("Bad Request Through Feign");
            case 401:
                log.error("Error in request went through feign client");
                //handle exception
                return new Exception("Unauthorized Request Through Feign");
            case 404:
                log.error("Error in request went through feign client");
                //handle exception
                return new Exception("Unidentified Request Through Feign");
            default:
                log.error("Error in request went through feign client");
                //handle exception
                return new Exception("Common Feign Exception");
        }
    }

}

Here I haven’t handled the exception, But in your case, you could create custom exceptions and handle these errors with whatever the exception you like on cases which added here.

Then, we need to introduce this error decoder into the feign configuration which we created earlier by adding following,

@Bean
public ErrorDecoder errorDecoder() { return new FeignCustomErrorDecoder();}

Reading original error message from feign error decoder

Here as what we have defined feign doesn’t give you the original error message which coming from the third party API. But we can read the original error using feign decoder as below.

package com.javatodev.feign.config;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.javatodev.feign.rest.response.FeignExceptionMessage;

import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;

import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class FeignCustomErrorDecoder implements ErrorDecoder {

    @Override public Exception decode(String methodKey, Response response) {

        //START DECODING ORIGINAL ERROR MESSAGE
        String erroMessage = null;
        Reader reader = null;

        //capturing error message from response body.
        try {
            reader = response.body().asReader(StandardCharsets.UTF_8);
            String result = IOUtils.toString(reader);
            ObjectMapper mapper = new ObjectMapper();
            mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
            FeignExceptionMessage exceptionMessage = mapper.readValue(result,
                FeignExceptionMessage.class);

            erroMessage = exceptionMessage.getMessage();

        } catch (IOException e) {
            log.error("IO Exception on reading exception message feign client" + e);
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException e) {
                log.error("IO Exception on reading exception message feign client" + e);
            }
        }

        //END DECODING ORIGINAL ERROR MESSAGE
        
        switch (response.status()) {
            case 400:
                log.error("Error in request went through feign client {} ", erroMessage);
                //handle exception
                return new Exception("Bad Request Through Feign");
            case 401:
                log.error("Error in request went through feign client {} ", erroMessage);
                //handle exception
                return new Exception("Unauthorized Request Through Feign");
            case 404:
                log.error("Error in request went through feign client {} ", erroMessage);
                //handle exception
                return new Exception("Unidentified Request Through Feign");
            default:
                log.error("Error in request went through feign client {} ", erroMessage);
                //handle exception
                return new Exception("Common Feign Exception");
        }
    }

}

Conclusion

In this article we’ve discussed How to Use Feign Client in Spring Boot along with few more additional configurations, value additions to the feign client setup with spring boot like error handling, defining configurations, etc.

You can find source codes for this tutorial from our Github.