Open Feign in Action

This is second part (Open Feign in Action) of the Open Feign tutorial series. You can find the first part of this tutorial here.

Overview

In this tutorial we will see Open Feign in Action. In last tutorial we saw how Feign makes it easy to write REST clients, in this tutorial I will show how to use Feign with different types of API’s.

For this tutorial we will use this Demo Server Application which uses Basic Authentication and provides CRUD operations for Products.

Configuration

For this project we will use following dependencies,

  1. Feign Core/Client
  2. Feign Jackson Decoder

so following are the dependencies which we will put in our pom.xml –

        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-core</artifactId>
            <version>10.5.1</version>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-jackson</artifactId>
            <version>10.5.1</version>
        </dependency>

Demo Server Configuration

To setup our demo server, use following steps,

  1. Clone Demo Server repository
  2. Built it using command “mvn package” and start the server using “java -jar demoserver-0.0.1-SNAPSHOT.jar” command.

Using Feign Client

Now lets get started with building our Feign client. The demo server that we are using runs on port 8080 and provides following 4 API’s –

  1. List products
  2. Create product
  3. Update product
  4. Delete product

and project uses Basic authentication mechanism for User authentication. Default username is “sachin” and password is “password”, so we have to send these values in Authorization header.

We will start with List products API and for this we will use @RequestLine annotation for defining Request method and URL. This time, as we have to send information in headers, we will use @Headers annotation from feign,

public interface ProductServiceClient {

    @RequestLine("GET /products")
    @Headers("Authorization: Basic {token}")
    List<Product> getAllProducts(@Param("token") String token);

}

As we see in above code, we are using GET request method and we are also sending Authorization header with token value which will be provided by the caller of the method. Above method returns list of Product, following is the source code for Product class.

public class Product {

    private Long id;
    private String name;
    private String description;
    private String model;
    private Long quantity;
    
    // Getters and setters with toString method
}

Now lets build the instance of our REST client in our main method,

public static void main(String[] args) {
    ProductServiceClient productServiceClient = Feign.builder()
            .encoder(new JacksonEncoder())
            .decoder(new JacksonDecoder())
            .target(ProductServiceClient.class, "http://localhost:8080");

    String encodedToken = Base64
            .getEncoder()
            .encodeToString("sachin:password".getBytes());
    
    System.out.println(productServiceClient.getAllProducts(encodedToken));
}

Now if you run this code you will see that it prints just an empty array! “[]”. That’s because the demo server we are using does not have any products created by default. So lets add a method in our REST client to create new products.

@RequestLine("POST /products")
@Headers({"Authorization: Basic {token}",
        "Content-type: application/json"})
Product createProduct(@Param("token") String token, Product product);

As you can see, we are now sending one more new header which specifies the type of the content we are sending. As we are using JacksonEncoder, parameter “Product” will be converted into JSON object. Here is our updated main method which creates a product first and then fetches the list of products from server and prints it,

public static void main(String[] args) {
    ProductServiceClient productServiceClient = Feign.builder()
            .encoder(new JacksonEncoder())
            .decoder(new JacksonDecoder())
            .target(ProductServiceClient.class, "http://localhost:8080");

    String encodedToken = Base64
            .getEncoder()
            .encodeToString("sachin:password".getBytes());

    createDummyProduct(encodedToken, productServiceClient);

    System.out.println(productServiceClient.getAllProducts(encodedToken));
}

private static Product createDummyProduct(String token, ProductServiceClient client) {
    Product product = new Product();
    product.setName("Dummy Product");
    product.setDescription("My dummy product");
    product.setModel("Dummy-ABC");
    product.setQuantity(2L);

    return client.createProduct(token, product);
}

Executing above code would print the result as following,

[Product{id=1, name='Dummy Product', description='My dummy product', model='Dummy-ABC', quantity=2}]

One thing which you may have noticed that in both the requests we sending the token and it is kind of redundant and it would have been nice to set it once instead in all the requests/methods.

Feign Request Interceptors

Feign provides a way to do it using Request Interceptors. Request interceptors can modify the request after it is created and before it is sent. In fact for our requirement it provides a built-in interceptor i.e. BasicAuthRequestInterceptor. Following is the BasicAuthRequestInterceptor code,

public class BasicAuthRequestInterceptor implements RequestInterceptor {

  private final String headerValue;

  public BasicAuthRequestInterceptor(String username, String password) {
    this(username, password, ISO_8859_1);
  }

  public BasicAuthRequestInterceptor(String username, String password, Charset charset) {
    checkNotNull(username, "username");
    checkNotNull(password, "password");
    this.headerValue = "Basic " + base64Encode((username + ":" + password).getBytes(charset));
  }

  private static String base64Encode(byte[] bytes) {
    return Base64.encode(bytes);
  }

  @Override
  public void apply(RequestTemplate template) {
    template.header("Authorization", headerValue);
  }
}

As you can see it basically does the same thing which we were doing in our code. So let’s use this interceptor to remove our redundant code. In our “main” and remove the authorization header from our REST client. Updated code of main method would be,

public static void main(String[] args) {
    ProductServiceClient productServiceClient = Feign.builder()
            .encoder(new JacksonEncoder())
            .decoder(new JacksonDecoder())
            .requestInterceptor(new BasicAuthRequestInterceptor("sachin", "password"))
            .target(ProductServiceClient.class, "http://localhost:8080");

    createDummyProduct(productServiceClient);

    System.out.println(productServiceClient.getAllProducts());
}

private static Product createDummyProduct(ProductServiceClient client) {
    Product product = new Product();
    product.setName("Dummy Product");
    product.setDescription("My dummy product");
    product.setModel("Dummy-ABC");
    product.setQuantity(2L);

    return client.createProduct(product);
}

Now you might have guessed already for the pending requests, we just have to add new methods in the REST client and those will be ready without writing any implementation for it!

Here is the complete Feign client code for list, create, update and delete product –

public interface ProductServiceClient {

    @RequestLine("GET /products")
    List<Product> getAllProducts();

    @RequestLine("POST /products")
    @Headers("Content-type: application/json")
    Product createProduct(Product product);

    @RequestLine("PUT /products/{productId}")
    @Headers("Content-type: application/json")
    Product updateProduct(@Param("productId") Long productId, Product product);

    @RequestLine("DELETE /products/{productId}")
    @Headers("Content-type: application/json")
    Product deleteProduct(Long productId);

}

Conclusion

In this tutorial we saw how Feign makes it easy to write REST clients and removes boilerplate code that we have to write and test every time when we write REST client by our own.