Configure Feign Client in Spring Boot Configure Feign Client in Spring Boot

Page content

In this article, we’ll learn how to configure a FeignClient in your Spring Boot project to consume RESTFul APIs from other services.

Overview

FeignClient is a Declarative REST Client in Spring Boot Web Application. Declarative REST Client means you just give the client specification as an Interface and spring boot takes care of the implementation for you.

FeignClient is used to consume RESTFul API endpoints exposed by third-party or microservice.

Feign vs RestTemplate

It is an alternative to RestTemplate and has the following advantages over RestTemplate:-

  1. Do not need to write implementation classes to call other services, just provide specifications as an Interface
  2. Client configurations such as encoding/decoding, timeout, and logging can just be done through property files.
  3. Client configurations can be done from Java Configuration file as well.
  4. Developed by Netflix. It has great support to work with other spring-boot cloud libraries such as Hystrix, Eureka and Ribbon
  5. Spring Boot provide support for writing test cases for Feign Client using WireMock
  6. Provide support for fallback data if API call fails.

Project Setup

For the initial setup of your Spring Boot project, you should use Spring Initializr. Choose the OpenFeign and Spring Web as dependencies and Contract Stub Runner as a test dependency.

Maven Project

You can click the below link to generate a Maven project with pre-selected dependencies:-

https://start.spring.io/#!type=maven-project&language=java&platformVersion=2.5.1.RELEASE&packaging=jar&jvmVersion=1.8&groupId=com.example&artifactId=api&name=api&description=Create%20Feign%20Client%20to%20consume%20RESTFul%20APIs&packageName=com.example.api&dependencies=cloud-feign,web,cloud-contract-stub-runner

<!-- to write web layer -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- to write web client using OpenFeign -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- to write test class using junit jupiter -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

<!-- to write integration test and mock stub using WireMock -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
  <scope>test</scope>
</dependency>

Gradle Project

Similarly, You can click the below link to generate a Gradle project with pre-selected dependencies:-

https://start.spring.io/#!type=gradle-project&language=java&platformVersion=2.5.1.RELEASE&packaging=jar&jvmVersion=1.8&groupId=com.example&artifactId=api&name=api&description=Create%20Feign%20Client%20to%20consume%20RESTFul%20APIs&packageName=com.example.api&dependencies=cloud-feign,web,cloud-contract-stub-runner

dependencies {
  // to write web layer
  implementation 'org.springframework.boot:spring-boot-starter-web'

  // to write web client using OpenFeign
  implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'

  // to write test class using junit jupiter
  testImplementation 'org.springframework.boot:spring-boot-starter-test'

  // to write integration test and mock stub using WireMock
  testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-stub-runner'
}

Feign Client Implementation Setup

Spring Cloud OpenFeign supports three underlying implementations for feign client:

  • Default
    This is enabled by default when no additional configuration is provided.
  • ApacheHttpClient
    This is enabled when we have feign.httpclient.enabled: true property in the configuration file and io.github.openfeign:feign-httpclient in the project classpath
    application.yml
    feign.httpclient.enabled: true
    pom.xml
    <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>
    build.gradle
    dependencies { implementation 'io.github.openfeign:feign-httpclient' }
  • OkHttpClient
    This is enabled when we have feign.okhttp.enabled: true property in the configuration file and io.github.openfeign:feign-okhttp in the project classpath
    application.yml
    feign.okhttp.enabled: true
    pom.xml
    <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency>
    build.gradle
    dependencies { implementation 'io.github.openfeign:feign-okhttp' }

Enable Feign Client

When you are working on a spring boot project, you have nothing much to do to enable FeignClient for your project. Make sure:-

  1. You have spring-boot-starter-web and spring-cloud-starter-openfeign dependencies in your pom.xml or build.gradle
  2. You are using @SpringBootApplication and @EnableFeignClients annotations at your application starter class file ApiApplication.

Spring Boot is opinionated, when it sees the web and openfeign dependencies in the classpath, it sets up all the necessary default configurations required for FeignClient and automatically scans for the classes annotated with @FeignClient

package com.example.api;

@SpringBootApplication
@EnableFeignClients
public class ApiApplication {

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

Create Feign Client

Next, we are going to create a FeignClient to consume from the RESTFul API endpoint. Let’s create a PostFeignClient interface -

  1. Annotate with @FeignClient which auto scans by spring boot application to generate feign client
  2. This FeignClient consumes the APIs from this URL: https://jsonplaceholder.typicode.com/

Let’s keep the API URL in the property file (e.g. application.yml) and use that property:-

client:
  post:
    baseUrl: https://jsonplaceholder.typicode.com
package com.example.api.client;

@FeignClient(name = "postFeignClient", url = "${client.post.baseUrl}")
public interface PostFeignClient {
    @GetMapping("/posts")
    List<Post> getAllPosts();

    @GetMapping("/posts/{postId}")
    Post getPostById(@PathVariable Long postId);

    @GetMapping("/posts")
    List<Post> getPostByUserId(@RequestParam Long userId);

    @PostMapping("/posts")
    Post createPost(Post post);

    @PutMapping("/posts")
    Post updatePost(Post post);

    @DeleteMapping("/posts/{postId}")
    Post deletePost(@PathVariable Long postId);
}

Feign Client Configuration

From Property file

Spring boot comes with default global configurations which are applied to all the feign clients you create. The good thing is you can change these global configurations from property files such as connection timeout, read timeout and the logger level

application.yml
feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: BASIC

You can also configure each feign client individually from the property file using feign client name or value. Remember, we created our feign client with name @FeignClient(name = "postFeignClient", ...)

The following properties can be configured for each feign client using name or value (e.g. postFeignClient):-

feign:
  client:
    config:
      postFeignClient:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: FULL
        errorDecoder: com.example.SimpleErrorDecoder
        retryer: com.example.SimpleRetryer
        requestInterceptors:
          - com.example.FooRequestInterceptor
          - com.example.BarRequestInterceptor
        decode404: false
        encoder: com.example.SimpleEncoder
        decoder: com.example.SimpleDecoder
        contract: com.example.SimpleContract

From Configuration Class file

We can also configure a FeignClient using a Configuration class. You have to pass this class as configuration while creating FeignClient e.g. @FeignClient(configuration = "FeignClientConfig.class", ...)

package com.example.api.client;

import com.example.api.config.FeignClientConfig;
@FeignClient(name = "postFeignClient", configuration = FeignClientConfig.class, url = "${client.post.baseUrl}")
public interface PostFeignClient {}

In FeignClientConfig, you can create beans of Decoder, Encoder, Logger, Contract, Feign.Builder and Client to override default beans created by Spring Boot. You can also create beans of Logger.Level, Retryer, ErrorDecoder and RequestInterceptor to include these features.

package com.example.api.config;

public class FeignClientConfig {}

Spring Cloud Netflix provides the following beans by default for feign (BeanType beanName: ClassName):

  • Decoder feignDecoder: ResponseEntityDecoder (which wraps a SpringDecoder)
  • Encoder feignEncoder: SpringEncoder
  • Logger feignLogger: Slf4jLogger
  • Contract feignContract: SpringMvcContract
  • Feign.Builder feignBuilder: HystrixFeign.Builder
  • Client feignClient: if Ribbon is enabled it is a LoadBalancerFeignClient, otherwise the default feign client is used.

Spring Cloud Netflix does not provide the following beans by default for feign, but still looks up beans of these types from the application context to create the feign client:

  • Logger.Level
  • Retryer
  • ErrorDecoder
  • Request.Options
  • RequestInterceptor
  • SetterFactory

Request Interceptor

You may come across a use case, where you need to pass Authorization Headers or Request Headers in API calls using Feign Client.

In such case, you need to provide a bean of type RequestInterceptor in a Feign Client Configuration class e.g. FeignClientConfig class as below:-

package com.example.api.config;

public class FeignClientConfig {

    /**
     * Enable this bean if you want to add headers in HTTP request
     */
    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> {
            requestTemplate.header("Content-Type", "application/json");
            requestTemplate.header("Accept", "application/json");
            requestTemplate.header("header_1", "value_1");
            requestTemplate.header("header_2", "value_2");
            requestTemplate.header("header_3", "value_3");
        };
    }

    /**
     * Enable this bean if you want to add basic Authorization header
     * for e.g. Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
     */
    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("username", "password");
    }
}

Note:- Do not annotate this class with @Configuration annotation, otherwise this configuration will become global i.e. all Feign Clients will inherit this config in that case.

Once you apply this configuration to FeignClient, all the requests made by that FeignClient will include the common headers and basic authorization header to outgoing HTTP requests.


Proxy Setup

You may come across a use case, where the Feign Client should use an HTTP proxy to make the outbound API call.

In such case, you need to override the default Client bean in a Feign Client Configuration class e.g. FeignClientConfig class as below:-

package com.example.api.config;

public class FeignClientConfig {

    /**
     * Enable this bean if you want to setup HTTP proxy for Default Feign Client
     */
    @Bean
    public Client feignClient() {
      return new Client.Proxied(null, null, 
          new Proxy(Proxy.Type.HTTP,
              new InetSocketAddress(proxyHost, proxyPort)));
    }

    /**
     * Enable this bean if you want to setup HTTP proxy for ApacheHttpClient Feign Client
     */
    @Bean
    public CloseableHttpClient feignClient() {
      return HttpClientBuilder.create().setProxy(
          new HttpHost(proxyHost, proxyPort)).build();
    }

    /**
     * Enable this bean if you want to setup HTTP proxy for OkHttpClient Feign Client
     */
    @Bean
    public OkHttpClient okHttpClient() {
      return new OkHttpClient.Builder()
          .proxy(new Proxy(Proxy.Type.HTTP, 
              new InetSocketAddress(proxyHost, proxyPort)))
          .build();
    }
}

Annotate this class with @Configuration annotation, if you want to apply this configuration to all Feign Clients in the project. Else do not annotate and apply this configuration to specific Feign Client, all the requests made by that FeignClient will use the given proxy to outgoing HTTP requests.


Feign Logging

A logger is created for each FeignClient by default. Feign logging only responds to the DEBUG level.

To enable the feign logging for all the feign clients, declare the logging level of the package name of client interfaces to DEBUG:-

logging:
  level:
    com.example.api.client: DEBUG

To enable the feign logging for specific FeignClient, declare the logging level to that interface to DEBUG:-

logging:
  level:
    com.example.api.client.PostFeignClient: DEBUG

Once you enable the feign logging by setting the logging level to DEBUG, you can further control the logging using loggerLevel configuration property which tells Feign how much to log per request. Choices are:

  • NONE, No logging (DEFAULT).
  • BASIC, Log only the request method and URL and the response status code and execution time.
  • HEADERS, Log the basic information along with request and response headers.
  • FULL, Log the headers, body, and metadata for both requests and responses.

E.g. BASIC loggerLevel for all feign clients and FULL loggerLevel for postFeignClient:-

feign:
  client:
    config:
      default:
        loggerLevel: BASIC
	  postFeignClient:
        loggerLevel: FULL

Consume the Feign Client

Now that we have created feign client, let’s create a service layer class PostService and its implementation PostServiceImpl to consume these APIs using the feign client

public interface PostService {

	List<Post> getAllPosts();
	Post getPostById(Long postId);
	List<Post> getAllPostsByUserId(Long userId);
	Post createPost(Post post);
	void updatePost(Long postId, Post post);
	void deletePost(Long postId);
}
@Service
public class PostServiceImpl implements PostService {

    @Autowired
    private PostFeignClient postFeignClient;

    @Override
    public List<Post> getAllPosts() {
        return postFeignClient.getAllPosts();
    }

    @Override
    public Post getPostById(Long postId) {
        return postFeignClient.getPostById(postId);
    }

    @Override
    public List<Post> getAllPostsByUserId(Long userId) {
        return postFeignClient.getPostByUserId(userId);
    }

    @Override
    public Post createPost(Post post) {
        return postFeignClient.createPost(post);
    }

    @Override
    public void updatePost(Long postId, Post post) {
        postFeignClient.updatePost(post);
    }

    @Override
    public void deletePost(Long postId) {
        postFeignClient.deletePost(postId);
    }
}

Now since we have created our service class and consumed APIs using feign client. Let’s create a controller PostController to test our feign client.

@RestController
@RequestMapping("/posts")
public class PostController {

    @Autowired
    private PostService postService;

    @GetMapping
    public List<Post> getAllPosts() { return postService.getAllPosts(); }

    @GetMapping("/{postId}")
    public Post getPostById(@PathVariable Long postId) { return postService.getPostById(postId); }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Post createPost(Post post) { return postService.createPost(post); }

    @PutMapping("/{postId}")
    @ResponseStatus(HttpStatus.OK)
    public void updatePost(@PathVariable Long postId, Post post) { postService.updatePost(postId, post); }

    @DeleteMapping("/{postId}")
    @ResponseStatus(HttpStatus.OK)
    public void deletePost(@PathVariable Long postId) { postService.deletePost(postId); }
}

Let’s test our controller endpoint from the browser to see if Feign client is working.

Feign Client Endpoint Testing

That’s it. You have successfully created and tested feign client to consume APIs from a given endpoint.

Unit Test for Feign Client

You should always write test cases for your Feign Client. Spring Boot Cloud module spring-cloud-contract lets you use WireMock in your test cases to mock the API data.

If your Spring Boot application is using the default Tomcat embedded server then you can add spring-cloud-starter-contract-stub-runner dependency to your maven (or gradle) and add @AutoConfigureWireMock at the class level to use Wiremock in your tests.

Wiremock runs as a stub server and you can register stub behavior using a Java API or via static JSON declarations as part of your test. The following code shows an example:

application-test.yml

We created a test configuration file so that our FeignClient calls https://localhost:9091 to get the data

client:
  post:
    baseUrl: http://localhost:9091
Unit Test

We tell WireMock to run a stub on local port 9091 to serve mock data. This way our FeignClient starts receiving the data from the mocked stub.

package com.example.api.client;

@ActiveProfiles("test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWireMock(port = 9091)
public class PostFeignClientTest {

    @Autowired
    PostFeignClient postFeignClient;

    @Test
    public void getAllPosts_whenValidClient_returnValidResponse() throws Exception {
        // Using WireMock to mock client API:
        stubFor(get(urlEqualTo("/posts"))
                .willReturn(aResponse()
                        .withStatus(HttpStatus.OK.value())
                        .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
                        .withBody(read("stubs/posts.json"))));

        List<Post> posts = postFeignClient.getAllPosts();
        Post post = posts.get(0);

        // We're asserting if WireMock responded properly
        assertThat(posts).hasSize(10);
        assertThat(post.getId()).isEqualTo(1);
        assertThat(post.getUserId()).isEqualTo(1);
        assertThat(post.getTitle()).isEqualTo("title");
        assertThat(post.getBody()).isEqualTo("body");
    }

    private String read(String location) throws IOException {
        return IOUtils.toString(new ClassPathResource(location).getInputStream(), StandardCharsets.UTF_8);
    }
}

Download the complete source code for the examples in this post from github/springboot-api