I just announced the Master Class of my "REST With Spring" Course:
1. Overview
In this tutorial we will introduce and explain 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 will be provisioned at runtime.
2. Example
We will present an example of a book store service REST API, that is queried and tested based on the Feign HTTP client.
Before we build a sample Feign client, we’ll add the needed dependencies and start-up the REST service.
The book store service example can be cloned from here.
After downloading the service application, we’ll run it with:
$> mvn install spring-boot:run
3. Setup
First we’ll create a new Maven project and include this dependencies:
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> <version>9.3.1</version> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-gson</artifactId> <version>9.3.1</version> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-slf4j</artifactId> <version>9.3.1</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, you’ll need your favorite, SLF4J-supported logger implementation on your classpath.
Before we continue to create our client interface, we’ll setup a Book model for holding our 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 need a simple wrapper class:
public class BookResource { private Book book; // standard constructor, getters and setters }
Note: We‘ll 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 books. Don’t forget to prefix your calls with ‘/api’, which is the servlet-context defined in the application.properties:
$> curl http://localhost:8081/api/books
we will 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
Now we’ll define our Feign client.
We will use the @RequestLine annotation to specify the HTTP verb and a path part as argument, and theparameters 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 an unit test class, containing three @Test methods, to test our client. The test will use static imports from the packages 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")); }
These tests are pretty self-explanatory. To run it, simply execute the Maven test goal:
$> mvn test
7. Further Reading
If you need some kind of fallback, in case of service unavailability, you can add HystrixFeign to your classpath and build your client with the HystrixFeign.builder() instead.
To learn more about Hystrix, please follow this dedicated tutorial series.
If you want to integrate Spring Cloud Netflix Hystrix with Feign, you can read more about this here.
It’s also possible to add client-side load-balancing and/or service discovery to your client.
The former is done by adding Ribbon to your classpath and call the builder like so:
BookClient bookClient = Feign.builder() .client(RibbonClient.create()) .target(BookClient.class, "http://localhost:8081/api/books");
For service discovery you have to build up your service with Spring Cloud Netflix Eureka enabled. Then simply integrate with Spring Cloud Netflix Feign and you get Ribbon load-balancing for free. More about this can be found here.
8. Conclusion
This article explained how to build a declarative HTTP client using Feign to consume text-based APIs.
As usual, you’ll find the sources on GitHub.