Simplify REST client and reduce boilerplate code with Feign

Intro

RestTemplate remains the most popular way to consume RESTful Web Services in Spring Framework. Although we are all aware that the library is not perfectly designed and numerous arguments in method signature contradict commonly known clean-code principles, we’ve just got used to it.

Fortunately, there is an alternative called Feign! With Feign we don’t litter our business logic with details of the HTTP client. We declare what REST client should do, instead of how it should be done.

Advantages of using Feign:

  • simple configuration by annotating interface
  • no need to write unit tests since there is no implementation (although integration tests are recommended!)
  • highly compatible and easily configurable
  • built-in Hystrix fall back mechanism
  • adapted to work in microservice environment (easy integration with Eureka and Ribbon providing Client-Side Load Balancing)

Creating Feign Client

Now it is time to dive into the world of Feign and create the first client. The dependency you need is spring-cloud-starter-openfeign and you are ready to go! Let’s start with enabling feign clients in our application.

@SpringBootApplication
@EnableFeignClients
public class FeignClientDemoApplication {

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

Then we declare the actual client.

@FeignClient(name="users", url = "${client.url}")
public interface UserClient {

    @RequestMapping(method = RequestMethod.GET, value = "/users")
    List<User> getUsers();
}

Simple, right? We’ve just created a working REST client! Currently it has only one method that returns a list of users.

Let’s go one step further and implement PUT method to update user. Imagine that the target API is secured with Basic Authorization and we’d like our client to log full information. In addition, we would like to have a custom error handler.

Firstly, we start with updating client class. New method updateUser was declared. It takes two arguments: the first one is user identifier, the second one is simple POJO class representing updated user object. In annotation we also defined, that we want to send data in JSON format. Additionally, we defined custom configuration class on the interface level.

@FeignClient(name="users", url = "${client.url}", configuration = UserClientConfiguration.class)
public interface UserClient {

    @RequestMapping(method = RequestMethod.GET, value = "/users")
    List<User> getUsers();

    @RequestMapping(method = RequestMethod.PUT, value = "/users/{userId}", consumes = "application/json")
    void updateUser(@PathVariable("userId") Long userId, User user);
}

Second step is to implement the configuration and create all required beans. We will use an Interceptor to enable adding Authorization Header to all requests. Next bean overrides the default logging level and the last one creates a custom error handler.

@Configuration
public class UserClientConfiguration {

    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("username","password");
    }

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

    @Bean
    public ErrorDecoder customErrorHandler() {
        return new CustomErrorHandler();
    }
}

And what else is missing is the implementation of the custom error handler.

public class CustomErrorHandler implements ErrorDecoder {

    @Override
    public Exception decode(String methodKey, Response response) {
        switch (response.status()) {
            case 400:
                return new BadRequestException();
            case 401:
                return new UserNotAuthorizedException();
            default:
                return new Exception("Another Error");
        }
    }
}

Feign Configuration

To configure Feign Client, Spring looks up beans named below, the white ones are provided by default, grey ones must be provided additionally, when needed.

Feign Client can be also configured using configuration properties like presented below.

feign:
  client:
    config:
      users:
        loggerLevel: full
        errorDecoder: com.feign.demo.CustomErrorHandler
        retryer: com.feign.demo.CustomRetryer
        contract: com.feign.demo.CustomContract
        requestInterceptors:
          - com.feign.demo.CustomRequestInterceptor
        encoder: com.feign.demo.CustomEncoder
        decoder: com.feign.demo.CustomDecoder

Testing

Feign client can easily be tested using WireMockServer provided by spring-cloud-starter-contract-stub-runner dependency. We must only define a stub expected behavior and et voilà! We are ready to call our Feign client and verify request and response.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWireMock(port = 9092)
class UserClientTest {

    @Autowired
    public UserClient userClient;

    @Test
    void getUsers_whenValidClient_returnUsersList() {
        // given
        stubFor(get(urlEqualTo("/users"))
                .willReturn(aResponse()
                        .withStatus(HttpStatus.OK.value())
                        .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
                        .withBody(readFile("getUsers_response.json"))));
        // when
        List<User> users = userClient.getUsers();

        // then
        assertThat(users).isNotNull().isNotEmpty();
        assertThat(users).hasSize(4);

        User user = users.get(0);
        assertThat(user.getUserId()).isEqualTo(1);
        assertThat(user.getLogin()).isEqualTo("user_feign_1");
    }

    @SneakyThrows
    private String readFile(String fileName) {
        return new String(getClass().getClassLoader().getResourceAsStream(fileName)
								.readAllBytes());
    }
}

127.0.0.1 - GET /users

Authorization: [Basic dXNlcm5hbWU6cGFzc3dvcmQ=]
Accept: [*/*]
User-Agent: [Java/11.0.10]
Host: [localhost:9092]
Connection: [keep-alive]

Matched response definition:
{
  "status" : 200,
  "body" : "[\n  {\"userId\": 1, \"login\": \"user_feign_1\"},\n  {\"userId\": 2, \"login\": \"user_feign_2\"},\n  {\"userId\": 3, \"login\": \"user_feign_3\"},\n  {\"userId\": 4, \"login\": \"user_feign_4\"}\n]",
  "headers" : {
    "Content-Type" : "application/json"
  }
}

Response:
HTTP/1.1 200
Content-Type: [application/json]
Matched-Stub-Id: [1e21892a-4f5e-4255-9db7-3856ee5470d4]

Conclusions

Feign is a powerful library, created to simplify implementing API clients. Additionally, it prevents from generating boilerplate code and lets developer focus on the business logic rather than paying too much attention to HTTP client.

I hope you use Feign in your next project! You can find full demo source code here

Interesting? Share the article with your friends!
Maciej Matjas
Technical Consultant Application Support