This article is going to cover about Spring 5 WebClient, a non-blocking, reactive client for HTTP requests with Reactive Streams back pressure.
1. Introduction To Spring 5 WebClient
The WebClient is a non-blocking, reactive HTTP client which has been introduced in Spring 5 and is included in the spring-webflux module. Following are characteristics of the WebClient:
- Provides a higher level API over HTTP client libraries. By default, it uses Reactor Netty
- Returns Reactor Flux or Mono for output and accepts Reactive Streams Publisher as input (see Reactive Libraries).
- Shares HTTP codecs and other infrastructure with the server functional
web framework.
By comparison to the RestTemplate, the Spring 5 WebClient has some more advantages:
- It offers a more functional
- Enables fluent API that taking full advantage of Java 8 lambdas.
- Supports both sync and async scenarios, including streaming, and brings the efficiency of non-blocking I/O.
2. Spring 5 WebClient API
2.1. Create A WebClient Object
Let’s see how we can create a WebClient object:
1 |
WebClient client = WebClient.create("http://localhost:8080/v1"); |
We have just configured a base URI for requests performed through the client to avoid repeating the same host, port, base path, or even query parameters with every request.
2.2. Exchange A Request With The exchange() Method
To exchange a request, we can use the exchange() method as the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@Test public void exchangeTest() { WebClient client = WebClient.create("http://localhost:8080/v1"); long id = 1; Mono<ClientResponse> response = client.get().uri("/books/{id}", id).accept(MediaType.APPLICATION_JSON).exchange(); assertEquals(HttpStatus.OK, response.block().statusCode()); Mono<ResponseEntity<Book>> resEntity = client.get().uri("/books/{id}", id) .accept(MediaType.APPLICATION_JSON).exchange().flatMap(res -> res.toEntity(Book.class)); assertEquals(HttpStatus.OK, resEntity.block().getStatusCode()); } |
The method exchanges the request for a ClientResponse with full access to the response status and headers before extracting the body.
2.3. Exchange A Request With The retrieve() Method
Another way to exchange a request with the WebClient is to use the retrieve() method:
1 2 3 4 5 6 7 8 9 10 11 12 |
@Test public void retrieveTest() { WebClient client = WebClient.create("http://localhost:8080/v1"); long id = 1; Mono<Book> response = client.get().uri("/books/{id}", id).accept(MediaType.APPLICATION_JSON) .retrieve().bodyToMono(Book.class); assertNotNull(response.block()); } |
The retrieve() method is a variant of the exchange() method that provides the shortest path to retrieving the full response (i.e. status, headers, and body) where instead of returning Mono<ClientResponse> it exposes shortcut methods to extract the response body.
3. Spring 5 WebClient Examples
3.1. Preparation
Assume that we have a REST API to manage Book, produces and consumes JSON data with some basic API(s) as follows:
3.1.1. Read All Books
1 |
GET http://localhost:8080/v1/books |
3.1.2. Read A Specific Book
1 |
GET http://localhost:8080/v1/books/{id} |
3.1.3. Create a new book
1 |
POST http://localhost:8080/v1/books |
3.1.4. Update a book
1 |
PUT http://localhost:8080/v1/books/{id} |
3.1.5. Delete a book
1 |
DELETE http://localhost:8080/v1/books/{id} |
3.1.6. Source Code
The sample source code of the above REST API can be found on my Github project.
3.1.7. The Book POJO
We’re going to define a Book class which represent the response from the above REST API:
1 2 3 4 5 6 |
public class Book { private Long id; private String name; private String author; // All getters and setters } |
3.2. Maven Dependency
We’re going to use Spring Boot 2.0.0.M5 with spring-boot-starter-webflux. Let’s see all Maven dependencies needed in the following part of the pom.xml file:
1 2 3 4 5 6 7 8 9 10 11 12 |
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> |
3.3. Make An HTTP GET Request
In the following example, we’re going to make an HTTP GET request to the REST API to retrieve all the books:
1 2 3 4 5 6 7 8 9 10 |
@Test public void httpGetTest() { WebClient client = WebClient.create("http://localhost:8080/v1"); Flux<Book> response = client.get().uri("/books").accept(MediaType.APPLICATION_JSON).retrieve() .bodyToFlux(Book.class); List<Book> books = response.collectList().block(); assertTrue(books.size() > 0); } |
And another example to retrieve a single book:
1 2 3 4 5 6 7 8 9 10 |
@Test public void httpGetSingleTest() { WebClient client = WebClient.create("http://localhost:8080/v1"); long id = 1; Mono<Book> response = client.get().uri("/books/{id}", id).accept(MediaType.APPLICATION_JSON) .retrieve().bodyToMono(Book.class); Book book = response.block(); assertEquals(id, book.getId().longValue()); } |
3.4. Make An HTTP POST Request (Create A Book)
To make an HTTP POST request, at first, we need to call the post() method of the WebClient in order to prepare an HTTP POST request. Then we optionally provide the body for the request and call the retrieve() or exchange() method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@Test public void httpPostTest() { WebClient client = WebClient.create("http://localhost:8080/v1"); Book spring5Book = new Book("Spring 5. 0 Microservices", "Rajesh R. V"); Mono<Book> spring5Mono = Mono.just(spring5Book); Mono<Book> response = client.post().uri("/books").accept(MediaType.APPLICATION_JSON) .body(spring5Mono, Book.class).retrieve().bodyToMono(Book.class); Book createdBook = response.block(); assertTrue(createdBook.getId() > 0); assertEquals(spring5Book.getName(), createdBook.getName()); assertEquals(spring5Book.getAuthor(), createdBook.getAuthor()); } |
3.4. Make An HTTP PUT Request (Update A Book)
To make an HTTP PUT request, at first, we need to call the put() method of the WebClient in order to prepare an HTTP PUT request. Then we optionally provide the body for the request and call the retrieve() method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
@Test public void httpPutTest() { WebClient client = WebClient.create("http://localhost:8080/v1"); Book proSpringBook = new Book("Pro Spring Boot", ""); Mono<Book> proSpring5Mono = Mono.just(proSpringBook); // create the book Mono<Book> response = client.post().uri("/books").accept(MediaType.APPLICATION_JSON) .body(proSpring5Mono, Book.class).retrieve().bodyToMono(Book.class); proSpringBook = response.block(); // update the author of the book proSpringBook.setAuthor("Gutierrez, Felipe"); proSpring5Mono = Mono.just(proSpringBook); response = client.put().uri("/books/{id}", proSpringBook.getId()).accept(MediaType.APPLICATION_JSON) .body(proSpring5Mono, Book.class).retrieve().bodyToMono(Book.class); Book updatedBook = response.block(); assertEquals(proSpringBook.getId(), updatedBook.getId()); assertEquals(proSpringBook.getName(), updatedBook.getName()); assertEquals(proSpringBook.getAuthor(), updatedBook.getAuthor()); } |
Notice that in above example, firstly we create a Book without the Author in the first request to the server. After that, we update the author for the book in the second request.
3.5. Make An HTTP DELETE Request (delete a book)
To make an HTTP DELETE request, at first, we need to call the delete() method of the WebClient in order to prepare an HTTP DELETE request. Then we optionally supply the data requested by the REST API for the request and call the retrieve() or exchange() method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@Test public void httpDeleteTest() { WebClient client = WebClient.create("http://localhost:8080/v1"); Book mstSpringBook = new Book("Mastering Spring 5.0", "Ranga Rao Karanam"); Mono<Book> mstSpringMono = Mono.just(mstSpringBook); // create the book Mono<Book> response = client.post().uri("/books").accept(MediaType.APPLICATION_JSON) .body(mstSpringMono, Book.class).retrieve().bodyToMono(Book.class); mstSpringBook = response.block(); assertTrue(mstSpringBook.getId() > 0); // delete the book Mono<ClientResponse> clientRes = client.delete().uri("/books/{id}", mstSpringBook.getId()) .accept(MediaType.APPLICATION_JSON).exchange(); assertEquals(HttpStatus.NO_CONTENT, clientRes.block().statusCode()); } |
In the above example, we create a book first. Then we delete the book and validate the response.
3.6. Make An HTTP Request With Basic Authentication
To make an HTTP request with basic authentication, we can provide the WebClient a filter which adds an Authorization header for HTTP Basic Authentication, based on the given username and password. Notice that Spring 5 already provides us a basic authentication filter which can be found in the ExchangeFilterFunctions class.
Now, let’s see an example of using WebClient to make a request with basic authentication:
1 2 3 4 5 6 7 8 9 10 |
@Test public void basicAuthTest() { WebClient client = WebClient.builder().filter(basicAuthentication("user", "passwd")) .baseUrl("http://httpbin.org").build(); Mono<ClientResponse> response = client.get().uri("/basic-auth/user/passwd").accept(MediaType.APPLICATION_JSON).exchange(); assertEquals(HttpStatus.OK, response.block().statusCode()); } |
4. Conclusion
In this tutorial, we have just got through Spring 5 WebClient, a non-blocking, reactive client for HTTP requests. We’ve learned how to prepare for HTTP GET, POST, PUT, DELETE requests and exchange the request using the exchange() and retrieve() methods. We can see that the easiest way to exchange a request is to use the retrieve() method while we have more control over the response body when we use the exchange() method.
The sample code presented in this tutorial is available on my Github project. It’s a Maven based project, so it’s easy to be imported into IDEs such as IntelliJ, Eclipse, etc.
Below are other related articles:
Using Netflix Feign with Spring Cloud
Basic Authentication with Open Feign
File Uploading with Open Feign
Java REST Client Using Retrofit 2
Simple Java REST Client Using java.net.URL package
Java REST Client Using Spring RestTemplate
Java REST Client Using Apache HttpClient
Java REST Client Using Jersey Client
Java REST Client Using Resteasy Client
Java REST Client Using Resteasy Client Proxy Framework
Java REST Client Using Apache CXF Proxy-based API