Last Update: 15.01.2020. By Jens in Spring Boot
In this tutorial, we will take a look at the FeignClient and how to use it in a Spring Boot application.
FeignClient is a library for creating REST API clients in a declarative way. So, instead of manually coding clients for remote API and maybe using Springs RestTemplate we declare a client definition and the rest is generated during runtime for use.
We will build a small command line app, which simulates a full test for our previously created Kanban API. The sample app will create a new user, logs in, retrieves all boards and unregisters the user again. It captures the most common use case (POST, GET, DELETE + AuthN)
In this tutorial, we are going to use our Kanban API tutorial project. The RESTful API is showcased on Kanban backend, and we will use the running default implementation.
I will cover the endpoints as far as they are related to this tutorial in the corresponding sections. For a full API doc, see here.
The Kanban API was a tutorial I run on the Learnletter, and you can find all the pieces in the blog.
We use Maven for this tutorial. As we do not want to mess with version numbers, the easiest way is to include the Spring Cloud setup in the dependencyManagement in the Maven POM.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Now, we can add the dependency to Feign with a classical Spring Boot starter:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
The Feign client uses a declarative approach for accessing the API. To use it, we must first enable the Spring Cloud support for it on our Spring Boot Application with the @EnableFeignClients annotation at the class level on a @Configuration class.
@SpringBootApplication
@EnableFeignClients
public class FeignIntroductionApplication implements ApplicationRunner {
//omitted
}
Next step is to declare an interface for accessing our API. We name it KanbanClient as it will provide the methods for calling our remote API.
@FeignClient(name="KanbanClient", url= "https://kanbanbackend.herokuapp.com/")
public interface KanbanClient {
}
To turn it into a Feign client, we must set the @FeignClient annotation on the interface and give it a name with the name attribute and also set the remote URL with the url attribute. SpEL is supported here so we could externalize the values to property files. Instead of the URL, we could also use service discovery using Eureka here. However that is out of scope for this tutorial.
To define a remote call, we must declare a method for that and use some annotations from Spring MVC, which are typically used on @Controller on the server side. They act the same, just on the client side now.
So, let’s start with adding functionality.
Adding the @PostMapping annotation on a method and passing a parameter in it, will turn the method into a POST call. In the annotation, we provide the endpoint relative to the URL we set on the @FeignClient annotation.
@PostMapping(value = "/register")
String registerUser(User user);
User is a simple POJO with a username and password field. Feign, and Spring will automatically transform it into JSON.
The same principle, but we use the @GetMapping on the method. We also send the authentication header. The API uses X-Auth-Token.
@GetMapping("/boards")
List<Board> listBoards(@RequestHeader("X-Auth-Token") String authHeader);
The /boards endpoint will return a list of all Kanban boards of the user. Board is the POJO for that and contains only id and name.
Same again, just with a @PutMapping annotation this time.
@PutMapping("/board/{id}")
Board changeBoard(@RequestHeader("X-Auth-Token") String authHeader, @PathVariable("id") Long id, Board board);
We can change the name of a board by making a PUT to the endpoint of that board (/board/{id}). FOr the path variable see section Making Calls with Variables below.
Kind of boring, but it looks the same as the other, just with a @DeleteMapping annotation.
@DeleteMapping("/unregister")
ResponseEntity<Void> unregisterUser(@RequestHeader("X-Auth-Token") String authToken, Confirmation user);
This endpoint requires a Confirmation object with the user’s password to delete the account and will also just return a 200 if successful.
If our endpoint requires a variable based on the entity like ids, we can use the @PathVariable annotation on a method parameter. It behaves the same as with Spring MVC @Controllers.
The define variable can than be used in the endpoint declaration on the @PutMapping( like:
@PutMapping("/board/{id}")
Board changeBoard(@RequestHeader("X-Auth-Token") String authHeader, @PathVariable("id") Long id, Board board);
We will use the login endpoint for this. It requires the user credentials send as basic auth and will return a token for further authentication.
@PostMapping("/login")
ResponseEntity<Void> loginUser(@RequestHeader("Authorization") String authHeader);
The first way to pass additional information as header down is to add a method parameter with the @RequestHeader annotation on it. The value of the parameter will be set as the value of the HTTP header defined in the annotation.
In the case of authentication, it is the Authorization header. As a value, we give it the Basic auth encoded string.
In subsequent calls for the Kanban API, we will use the X-Auth-Token header with a token.
Response headers can’t be returned directly as the method return value, but we can use Spring’s ResponseEntity, which is a response wrapper.
When we call the /login endpoint successfully, it will return the auth token in a response header. The method will look like:
@PostMapping("/login")
ResponseEntity<Void> loginUser(@RequestHeader("Authorization") String authHeader);
The Void as a parametrized type is needed as our endpoint does not return anything in the response body.
We use Spring Session in the Kanban API, so the auth tokens are exchanged via the X-Auth-Token header.
To retrieve the value we call:
String token = response.getHeaders().getFirst("X-Auth-Token");
response is of type ResponseEntity.
We can always pass down authentication headers using the @RequestHeader annotation on each method. However, there is also an alternative way to specify that globally.
Like Spring MVC, Feign has an interceptor concept, which can be used to do specific stuff before a remote call. The entry point is the RequestInterceptor interface.
With Spring we just need to provide a Bean implementing that particular interface to the Spring context, and it will be automatically picked up.
Example:
@Bean
AuthInterceptor authFeign() {
return new AuthInterceptor();
}
class AuthInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header("Authorization", "<your token>");
}
}
Our interceptor is a Spring Bean, and thus we can use the power of Spring and externalize the authN info into properties or even retrieve it from a session scoped bean were we keep the information on a per-user basis.
Now, we can inject the KanbanClient into our code like any other Spring Bean. On startup, Spring Cloud will set up the Feign client for us and give us a regular Spring Proxy so we can simply start working with the remote end.
Our client using Feign looks like that in the end:
@FeignClient(name="KanbanClient", url= "https://kanbanbackend.herokuapp.com/")
public interface KanbanClient {
@PostMapping(value = "/register")
String registerUser(User user);
@DeleteMapping("/unregister")
ResponseEntity<Void> unregisterUser(@RequestHeader("X-Auth-Token") String authToken, Confirmation user);
@PostMapping("/login")
ResponseEntity<Void> loginUser(@RequestHeader("Authorization") String authHeader);
@GetMapping("/boards")
List<Board> listBoards(@RequestHeader("X-Auth-Token") String authHeader);
@PostMapping("/boards")
Board createBoard(@RequestHeader("X-Auth-Token") String authHeader, Board board);
@PutMapping("/board/{id}")
Board changeBoard(@RequestHeader("X-Auth-Token") String authHeader, @PathVariable("id") Long id, Board board);
}
The full working sample is on GitHub.
It will register a new user, log in, creates a board, change the board’s name, lists all boards and then unregisters the user at the end.