Configure Feign Client in Spring Boot
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:-
- Do not need to write implementation classes to call other services, just provide specifications as an Interface
- Client configurations such as encoding/decoding, timeout, and logging can just be done through property files.
- Client configurations can be done from Java Configuration file as well.
- Developed by Netflix. It has great support to work with other spring-boot cloud libraries such as Hystrix, Eureka and Ribbon
- Spring Boot provide support for writing test cases for Feign Client using WireMock
- 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:-
<!-- 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:-
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 havefeign.httpclient.enabled: true
property in the configuration file andio.github.openfeign:feign-httpclient
in the project classpathapplication.ymlfeign.httpclient.enabled: truepom.xml<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>build.gradledependencies { implementation 'io.github.openfeign:feign-httpclient' } - OkHttpClient
This is enabled when we havefeign.okhttp.enabled: true
property in the configuration file andio.github.openfeign:feign-okhttp
in the project classpathapplication.ymlfeign.okhttp.enabled: truepom.xml<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency>build.gradledependencies { 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:-
- You have
spring-boot-starter-web
andspring-cloud-starter-openfeign
dependencies in your pom.xml or build.gradle - You are using
@SpringBootApplication
and@EnableFeignClients
annotations at your application starter class fileApiApplication
.
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 -
- Annotate with
@FeignClient
which auto scans by spring boot application to generate feign client - 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 aSpringDecoder
)Encoder
feignEncoder:SpringEncoder
Logger
feignLogger:Slf4jLogger
Contract
feignContract:SpringMvcContract
Feign.Builder
feignBuilder:HystrixFeign.Builder
Client
feignClient: if Ribbon is enabled it is aLoadBalancerFeignClient
, 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.
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