The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:
Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
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>13.1</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-gson</artifactId>
<version>13.1</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
<version>13.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, 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: 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 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 an 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 that 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 using the builder:
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 we 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.
