Jackson Top

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

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we'll introduce Feign – a declarative HTTP client developed by Netflix.

Feign aims at simplifying HTTP API clients. Simply put, the developer needs only to declare and annotate an interface while the actual implementation is provisioned at runtime.

2. Example

Throughout this tutorial, we'll be using an example bookstore application that exposes the REST API endpoint.

We can easily clone the project and run it locally:

mvn install spring-boot:run

3. Setup

First, let's add the needed dependencies:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
    <version>10.11</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-gson</artifactId>
    <version>10.11</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-slf4j</artifactId>
    <version>10.11</version>
</dependency>

Besides the feign-core dependency (which is also pulled in), we'll use a few plugins, especially: feign-okhttp for internally using Square's OkHttp client to make requests, feign-gson for using Google's GSON as JSON processor and feign-slf4j for using the Simple Logging Facade to log requests.

To actually get some log output, we'll need our favorite, SLF4J-supported logger implementation on the classpath.

Before we proceed to create our client interface, first we'll set up a Book model for holding the data:

public class Book {
    private String isbn;
    private String author;
    private String title;
    private String synopsis;
    private String language;

    // standard constructor, getters and setters
}

NOTE: At least a “no arguments constructor” is needed by a JSON processor.

In fact, our REST provider is a hypermedia-driven API, so we'll additionally need a simple wrapper class:

public class BookResource {
    private Book book;

    // standard constructor, getters and setters
}

Note: Well keep the BookResource simple because our sample Feign client doesn't benefit from hypermedia features!

4. Server Side

To understand how to define a Feign client, we'll first look into some of the methods and responses supported by our REST provider.

Let's try it out with a simple curl shell command to list all the books. We need to remember to prefix all the calls with /api, which is the application's servlet-context:

curl http://localhost:8081/api/books

As a result, we'll get a complete book repository represented as JSON:

[
  {
    "book": {
      "isbn": "1447264533",
      "author": "Margaret Mitchell",
      "title": "Gone with the Wind",
      "synopsis": null,
      "language": null
    },
    "links": [
      {
        "rel": "self",
        "href": "http://localhost:8081/api/books/1447264533"
      }
    ]
  },

  ...

  {
    "book": {
      "isbn": "0451524934",
      "author": "George Orwell",
      "title": "1984",
      "synopsis": null,
      "language": null
    },
    "links": [
      {
        "rel": "self",
        "href": "http://localhost:8081/api/books/0451524934"
      }
    ]
  }
]

We can also query individual Book resource, by appending the ISBN to a get request:

curl http://localhost:8081/api/books/1447264533

5. Feign Client

Finally, let's define our Feign client.

We'll use the @RequestLine annotation to specify the HTTP verb and a path part as an argument. The parameters will be modeled using the @Param annotation:

public interface BookClient {
    @RequestLine("GET /{isbn}")
    BookResource findByIsbn(@Param("isbn") String isbn);

    @RequestLine("GET")
    List<BookResource> findAll();

    @RequestLine("POST")
    @Headers("Content-Type: application/json")
    void create(Book book);
}

NOTE: Feign clients can be used to consume text-based HTTP APIs only, which means that they cannot handle binary data, e.g. file uploads or downloads.

That's all! Now we'll use the Feign.builder() to configure our interface-based client. The actual implementation will be provisioned at runtime:

BookClient bookClient = Feign.builder()
  .client(new OkHttpClient())
  .encoder(new GsonEncoder())
  .decoder(new GsonDecoder())
  .logger(new Slf4jLogger(BookClient.class))
  .logLevel(Logger.Level.FULL)
  .target(BookClient.class, "http://localhost:8081/api/books");

Feign supports various plugins such as JSON/XML encoders and decoders or an underlying HTTP client for making the requests.

6. Unit Test

Let's create three test cases to test our client. Note, we use static imports for org.hamcrest.CoreMatchers.* and org.junit.Assert.*:

@Test
public void givenBookClient_shouldRunSuccessfully() throws Exception {
   List<Book> books = bookClient.findAll().stream()
     .map(BookResource::getBook)
     .collect(Collectors.toList());

   assertTrue(books.size() > 2);
}

@Test
public void givenBookClient_shouldFindOneBook() throws Exception {
    Book book = bookClient.findByIsbn("0151072558").getBook();
    assertThat(book.getAuthor(), containsString("Orwell"));
}

@Test
public void givenBookClient_shouldPostBook() throws Exception {
    String isbn = UUID.randomUUID().toString();
    Book book = new Book(isbn, "Me", "It's me!", null, null);
    bookClient.create(book);
    book = bookClient.findByIsbn(isbn).getBook();

    assertThat(book.getAuthor(), is("Me"));
}

7. Further Reading

If we need some kind of a fallback in case of the service unavailability, we could add HystrixFeign to the classpath and build our client with HystrixFeign.builder().

Check out this dedicated tutorial series to learn more about Hystrix.

Additionally, if we'd like to integrate Spring Cloud Netflix Hystrix with Feign, there's a dedicated article over here.

Moreover, it's also possible to add client-side load-balancing and/or service discovery to our client.

We could achieve this by adding Ribbon to our classpath and use the builder like so:

BookClient bookClient = Feign.builder()
  .client(RibbonClient.create())
  .target(BookClient.class, "http://localhost:8081/api/books");

For service discovery, we have to build up our service with Spring Cloud Netflix Eureka enabled. Then simply integrate with Spring Cloud Netflix Feign. As a result, we get Ribbon load-balancing for free. More about this can be found here.

8. Conclusion

In this article, we've explained how to build a declarative HTTP client using Feign to consume text-based APIs.

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

Jackson bottom

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

>> CHECK OUT THE COURSE
1 Comment
Oldest
Newest
Inline Feedbacks
View all comments
kholofelo Maloma
3 years ago

Very useful! easy to read and follow. I managed to read, and get it running in no more than 37 minutes.

Comments are closed on this article!