Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

If you have a few years of experience in the Java ecosystem, and you're interested in sharing that experience with the community (and getting paid for your work of course), have a look at the "Write for Us" page. Cheers. Eugen

1. Overview

In this tutorial, we’re going to describe Spring Cloud OpenFeign – a declarative REST client for Spring Boot apps.

Feign makes writing web service clients easier with pluggable annotation support, which includes Feign annotations and JAX-RS annotations.

Also, Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters as used in Spring Web.

And, a great thing about using Feign is that we don’t have to write any code for calling the service, other than an interface definition.

2. Dependencies

First, we’ll start by creating a Spring Boot web project and adding the spring-cloud-starter-openfeign dependency to our pom.xml file:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

Also, we’ll need to add the spring-cloud-dependencies:

 <dependencyManagement>
     <dependencies>
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

We can find the latest versions of spring-cloud-starter-openfeign and spring-cloud-dependencies on Maven Central.

3. Feign Client

Next, we need to add @EnableFeignClients to our main class:

@SpringBootApplication
@EnableFeignClients
public class ExampleApplication {

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

With this annotation, we enable component scanning for interfaces that declare they are Feign clients.

Then, we declare a Feign client using the @FeignClient annotation:

@FeignClient(value = "jplaceholder", url = "https://jsonplaceholder.typicode.com/")
public interface JSONPlaceHolderClient {

    @RequestMapping(method = RequestMethod.GET, value = "/posts")
    List<Post> getPosts();

    @RequestMapping(method = RequestMethod.GET, value = "/posts/{postId}", produces = "application/json")
    Post getPostById(@PathVariable("postId") Long postId);
}

In this example, we’ve configured a client to read from the JSONPlaceHolder APIs.

The value argument passed in the @FeignClient annotation is a mandatory, arbitrary client name, while with the url argument, we specify the API base URL.

Furthermore, since this interface is a Feign client, we can use the Spring Web annotations to declare the APIs that we want to reach out to.

4. Configuration

Now, it’s very important to understand that each Feign client is composed of a set of customizable components.

Spring Cloud creates a new default set on demand for each named client using the FeignClientsConfiguration class that we can customize as explained in the next section.

The above class contains these beans:

  • Decoder – ResponseEntityDecoder, which wraps SpringDecoder, used to decode the Response
  • Encoder – SpringEncoder, used to encode the RequestBody
  • Logger – Slf4jLogger is the default logger used by Feign
  • Contract – SpringMvcContract, which provides annotation processing
  • Feign-Builder – HystrixFeign.Builder used to construct the components
  • Client – LoadBalancerFeignClient or default Feign client

4.1. Custom Beans Configuration

In the event that we want to customize one or more of these beans, we can override them using a @Configuration class, which we then add to the FeignClient annotation:

@FeignClient(value = "jplaceholder",
  url = "https://jsonplaceholder.typicode.com/",
  configuration = MyClientConfiguration.class)
@Configuration
public class MyClientConfiguration {

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

In this example, we tell Feign to use OkHttpClient instead of the default one in order to support HTTP/2.

Feign supports multiple clients for different use cases, including the ApacheHttpClient, which sends more headers with the request – for example, Content-Length, which some servers expect.

In order to use these clients, let’s not forget to add the required dependencies to our pom.xml file, for example:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

We can find the latest versions of feign-okhttp and feign-httpclient on Maven Central.

4.2. Configuration Using Properties

Rather than use a @Configuration class, we can use application properties to configure Feign clients, as shown in this application.yaml example:

feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: basic

With this configuration, we’re setting the timeouts to 5 seconds and the logger level to basic for each declared client in the application.

Finally, we can create the configuration with default as the client name to configure all @FeignClient objects, or we can declare the feign client name for a configuration:

feign:
  client:
    config:
      jplaceholder:

In the event that we have both @Configuration bean and configuration properties, configuration properties will override @Configuration values.

5. Interceptors

Adding interceptors is another useful feature provided by Feign. 

The interceptors can perform a variety of implicit tasks, from authentication to logging, for every HTTP request/response.

So, in the snippet below, let’s declare a request interceptor that adds basic authentication to each request:

@Bean
public RequestInterceptor requestInterceptor() {
  return requestTemplate -> {
      requestTemplate.header("user", username);
      requestTemplate.header("password", password);
      requestTemplate.header("Accept", ContentType.APPLICATION_JSON.getMimeType());
  };
}

Also, to add the interceptor to the request chain, we just need to add this bean to our @Configuration class, or as we saw previously, declare it in the properties file:

feign:
  client:
    config:
      default:
        requestInterceptors:
          com.baeldung.cloud.openfeign.JSONPlaceHolderInterceptor

6. Hystrix Support

Feign supports Hystrix, so if we have enabled it, we can implement the fallback pattern.

With the fallback pattern, when a remote service call fails, rather than generating an exception, the service consumer will execute an alternative code path to try to carry out the action through another means.

To achieve the goal, we need to enable Hystrix adding feign.hystrix.enabled=true in the properties file.

This allows us to implement fallback methods that are called when the service fails:

@Component
public class JSONPlaceHolderFallback implements JSONPlaceHolderClient {

    @Override
    public List<Post> getPosts() {
        return Collections.emptyList();
    }

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

To let Feign know that fallback methods have been provided, we also need to set our fallback class in the @FeignClient annotation:

@FeignClient(value = "jplaceholder",
  url = "https://jsonplaceholder.typicode.com/",
  fallback = JSONPlaceHolderFallback.class)
public interface JSONPlaceHolderClient {
    // APIs
}

7. Logging

For each Feign client, a logger is created by default.

To enable logging, we should declare it in the application.properties file using the package name of the client interfaces:

logging.level.com.baeldung.cloud.openfeign.client: DEBUG

Or, if we want to enable logging only for one particular client in a package, we can use the full class name:

logging.level.com.baeldung.cloud.openfeign.client.JSONPlaceHolderClient: DEBUG

Note that Feign logging responds only to the DEBUG level.

The Logger.Level that we may configure per client indicates how much to log:

@Configuration
public class ClientConfiguration {
    
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.BASIC;
    }
}

There are four logging levels to choose from:

  • NONE – no logging, which is the default
  • BASIC – log only the request method, URL, and response status
  • HEADERS – log the basic information together with request and response headers
  • FULL – log the body, headers, and metadata for both request and response

8. Error Handling

Feign’s default error handler, ErrorDecoder.default, always throws a FeignException.

Now, this behavior isn’t always the most useful. So, to customize the Exception thrown, we can use a CustomErrorDecoder:

public class CustomErrorDecoder implements ErrorDecoder {
    @Override
    public Exception decode(String methodKey, Response response) {

        switch (response.status()){
            case 400:
                return new BadRequestException();
            case 404:
                return new NotFoundException();
            default:
                return new Exception("Generic error");
        }
    }
}

Then, as we’ve done previously, we have to replace the default ErrorDecoder by adding a bean to the @Configuration class:

@Configuration
public class ClientConfiguration {

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

9. Conclusion

In this article, we discussed Spring Cloud OpenFeign and its implementation in a simple sample application.

Moreover, we’ve seen how to configure a client, how to add interceptors to our requests, and how to handle errors using Hystrix and ErrorDecoder.

As usual, all code samples shown in this tutorial are available over on GitHub.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE