Spring 5 WebClient and WebTestClient tutorial with examples (2023)

Spring Developers, have you ever felt the need for oneasynchronous/non-blockingHTTP-Client mit aflowing functional styleAPI that was easy to use and efficient?

If so, then welcome to this article about WebClient, the new HTTP reactive client introduced in Spring 5.

How to use the web client

WebClient is part of Spring 5's reactive web framework called Spring WebFlux. To use WebClient you must have thespring-webfluxmodule in your project.

Add dependency in an existing Spring Boot project

If you have an existing Spring Boot project, you can use thespring-webfluxmodule by adding the following dependency in thepom.xmlfile -

<dependency> <The group ID>org.springframework.boot</The group ID> <artifactId>spring-boot-starter-webflux</artifactId></dependency>

Create a new project from Scratch

If you are creating a project from scratch, you can use to generate a starter projectspring-webfluxmodule from theSpring InitializrWebsite -

  1. Go tohttp://start.spring.io.
  2. SentenceArtifactAndgroupToWebclient-Demo.
  3. SentencePackageTocom.example.webclientdemo.
  4. Add toResponsive Web, Andvalidationdependencies.
  5. Clickto generateto generate and download the project.

Use of remote APIs with WebClient

Let's make things interesting and use WebClient to consume a Real World API.

In this article we will consumeGithub's APIswith web client. We use WebClient to perform CRUD operations on the user's Github repositories.

You can read and understand the bits and pieces of WebClient from scratch or download the entire demo project with all the examples fromGitHub.

Create an instance of WebClient

1. WebClient with the createcreate()Method

You can create an instance of WebClient usingcreate()factory method -

(Video) Consume Rest service using Spring 5 WebClient (Reactive programming) | Java Techie

WebClientwebClient= WebClient.create();

If you are only using APIs from a specific service, you can initialize WebClient with the baseUrl of that service like this:

WebClientwebClient= WebClient.create("https://api.github.com");

2. Create the WebClient with the WebClient Builder

WebClient also includes a builder that gives you a range of customization options including filters, standard headers, cookies, client connectors, etc. -

WebClientwebClient= WebClient.Baumeister() .baseUrl("https://api.github.com") .defaultHeader(HttpHeader.CONTENT TYPE, "application/vnd.github.v3+json") .defaultHeader(HttpHeader.USER-AGENT, "Spring 5 WebClient") .build();

Sending a request with WebClient and getting the response

Here's how you can use WebClient to create aRECEIVErequest toGithubs List Repositories API-

public Flow<GithubRepo> listGithubRepositories(lineusername, lineSign) { to returnwebClient.receive() .uri("/user/repos") .Header("Permit", "Basic " + Base64Utils .encodeToString((username+ ":" +Sign).getBytes(UTF_8))) .recall() .bodyToFlux(GithubRepo.Class);}

See how easy and concise the API calls are!

Suppose we have a class namedGithubRepowhich confirms Github's API response, the above function returns aFlowvonGithubRepoobjects.

Note that I'm usingGithub's basic authenticationMechanism for calling the APIs. It requires your Github username and a personal access token, which you can generatehttps://github.com/settings/tokens.

Using the exchange() method to get the response

Therecall()method is the easiest way to get the response body. However, if you want more control over the response, you can use theExchange()Method that has access to the wholeClientResponseincluding all headers and the body -

public Flow<GithubRepo> listGithubRepositories(lineusername, lineSign) { to returnwebClient.receive() .uri("/user/repos") .Header("Permit", "Basic " + Base64Utils .encodeToString((username+ ":" +Sign).getBytes(UTF_8))) .Exchange() .flatMapViele(customer response->customer response.bodyToFlux(GithubRepo.Class));}
(Video) 13 Using WebClient to make API calls - Spring Boot Microservices Level 1

Using parameters in the request URI

You can use parameters in the request URI and pass their values ​​separately in theuri()Function. All parameters are surrounded by curly brackets. The parameters are automatically replaced by the WebClient before the request is made -

public Flow<GithubRepo> listGithubRepositories(lineusername, lineSign) { to returnwebClient.receive() .uri("/user/repos?sort={sortField}&direction={sortDirection}", "Updated", "dismount") .Header("Permit", "Basic " + Base64Utils .encodeToString((username+ ":" +Sign).getBytes(UTF_8))) .recall() .bodyToFlux(GithubRepo.Class);}

Using the URIBuilder to construct the request URI

You can also gain full programmatic control over the request URI by using aUriBuilderso -

public Flow<GithubRepo> listGithubRepositories(lineusername, lineSign) { to returnwebClient.receive() .uri(uriBuilder->uriBuilder.Away("/user/repos") .QueryParam("Sort by", "Updated") .QueryParam("Direction", "dismount") .build()) .Header("Permit", "Basic " + Base64Utils .encodeToString((username+ ":" +Sign).getBytes(UTF_8))) .recall() .bodyToFlux(GithubRepo.Class);}

Passing the request body in WebClient requests

If you send the request body in the form of aMonoor aFlow, then you can pass it directly to theBody()method in the WebClient, otherwise you can just create a Mono/Flux from an object and pass it like this -

public Mono<GithubRepo> createGithubRepository(lineusername, lineSign, RepoRequestcreateRepoRequest) { to returnwebClient.Post() .uri("/user/repos") .Body(Mono.Only(createRepoRequest), RepoRequest.Class) .Header("Permit", "Basic " + Base64Utils .encodeToString((username+ ":" +Sign).getBytes(UTF_8))) .recall() .body to mono(GithubRepo.Class);}

If you have an actual value instead of aeditor(Flow/Mono), you can use the ...syncBody()Link method to pass the request body -

public Mono<GithubRepo> createGithubRepository(lineusername, lineSign, RepoRequestcreateRepoRequest) { to returnwebClient.Post() .uri("/user/repos") .syncBody(createRepoRequest) .Header("Permit", "Basic " + Base64Utils .encodeToString((username+ ":" +Sign).getBytes(UTF_8))) .recall() .body to mono(GithubRepo.Class);}

Finally, you can use various factory methods provided byBodyInserterconstruct class aBodyInserterobject and pass it into theBody()Method. TheBodyInserterClass contains methods for creating aBodyInserterof aObject,editor,Resource,form data,MultipartDataetc -

public Mono<GithubRepo> createGithubRepository(lineusername, lineSign, RepoRequestcreateRepoRequest) { to returnwebClient.Post() .uri("/user/repos") .Body(BodyInserter.fromObject(createRepoRequest)) .Header("Permit", "Basic " + Base64Utils .encodeToString((username+ ":" +Sign).getBytes(UTF_8))) .recall() .body to mono(GithubRepo.Class);}

Add filter functions

WebClient supports request filtering with aExchangeFilterFunction. You can use filter functions to intercept the request and modify it as you like. For example, you can use a filter function to add onepermitHeaders for each request or to log the details of each request.

TheExchangeFilterFunctiontakes two arguments -

  1. Thecustomer requestAnd
  2. The nextExchangeFilterFunctionin the filter chain.

It can change thatcustomer requestand call the next oneExchangeFilterFunctionin the filter chain to move on to the next filter or return the changed onecustomer requestdirectly to block the filter chain.

(Video) Spring WebFluxTest WebTestClient | Reactive Spring | Spring 5

1. Add basic authentication using a filter function

In all of the above examples, we include apermitHeaders for basic authentication with Github API. Since this is common to all requirements, you can add this logic in a filter function as you create itWebClient.

TheExchangeFilterFunctionsThe API already provides a filter for basic authentication. You can use it like this -

WebClientwebClient= WebClient.Baumeister() .baseUrl(GITHUB_API_BASE_URL) .defaultHeader(HttpHeader.CONTENT TYPE,GITHUB_V3_MIME_TYPE) .Filter(ExchangeFilterFunctions .BasicAuthentication(username,Sign)) .build();

Now you don't have to add thempermitheaders in each request. The filter function intercepts every WebClient request and adds this header.

2. Logging of all inquiries via a filter function

Let's look at an example of a customExchangeFilterFunction. We'll write a filter function to intercept and log each request -

WebClientwebClient= WebClient.Baumeister() .baseUrl(GITHUB_API_BASE_URL) .defaultHeader(HttpHeader.CONTENT TYPE,GITHUB_V3_MIME_TYPE) .Filter(ExchangeFilterFunctions .BasicAuthentication(username,Sign)) .Filter(logRequest()) .build();

Here is the implementation of thelogRequest()filter function -

Private ExchangeFilterFunction logRequest() { to return (customer request,next) -> {Logger.die Info("Inquiry: {} {}",customer request.Method(),customer request.URL());customer request.headers() .for every((Name,Values) ->Values.for every(Wert->Logger.die Info("{}={}",Name,Wert))); to returnnext.Exchange(customer request); };}

3. Using the factory methods ofRequestProcessor() and ofResponseProcessor() to create filters

The ExchangeFilterFunction API provides two factory methods namedofRequestProcessor()AndofResponseProcessor()for creating filter functions that intercept the request or response.

ThelogRequest()Filter function that we created in the previous section can be created withofRequestProcessor()factory method like this -

Private ExchangeFilterFunction logRequest() { ExchangeFilterFunction.ofRequestProcessor(customer request-> {Logger.die Info("Inquiry: {} {}",customer request.Method(),customer request.URL());customer request.headers() .for every((Name,Values) ->Values.for every(Wert->Logger.die Info("{}={}",Name,Wert))); to return Mono.Only(customer request); });} 

If you want to intercept the WebClient response, you can use theofResponseProcessor()Method to create a filter function like this -

Private ExchangeFilterFunction logResponseStatus() { to return ExchangeFilterFunction.ofResponseProcessor(customer response-> {Logger.die Info("response status {}",customer response.Statuscode()); to return Mono.Only(customer response); });}
(Video) Spring Boot Testing Mini-Series Part #5: WebClient Tests

Handling WebClient errors

Therecall()Method in WebClient throws aWebClientResponseExceptionwhenever a response with status code 4xx or 5xx is received.

You can customize this withonStatus()Methods like this -

public Flow<GithubRepo> listGithubRepositories() { to returnwebClient.receive() .uri("/user/repos?sort={sortField}&direction={sortDirection}", "Updated", "dismount") .recall() .onStatus(HTTPStatus::is4xxClientError,customer response-> Mono.Mistake(neu MyCustomClientException()) ) .onStatus(HTTPStatus::is5xxServerError,customer response-> Mono.Mistake(neu MyCustomServerException()) ) .bodyToFlux(GithubRepo.Class);}

Note that differentrecall()method thatExchange()method does not throw exceptions on 4xx or 5xx responses. You have to check the status codes yourself and handle them the way you want.

Handling WebClientResponseExceptions with a@ExceptionHandlerwithin the controller

You can use one@ExceptionHandlerto handle in your controllerWebClientResponseExceptionand return customers an appropriate response like this -

@ExceptionHandler(WebClientResponseException.Class)public ResponseEntity<line> handleWebClientResponseException(WebClientResponseExceptionex) {Logger.Mistake("Error from WebClient - Status {}, Text {}",ex.getRawStatusCode(),ex.getResponseBodyAsString(),ex); to return ResponseEntity.Status(ex.getRawStatusCode()).Body(ex.getResponseBodyAsString());}

Testing REST APIs with Spring 5 WebTestClient

WebTestClient contains request methods that are similar to WebClient. Additionally, it includes methods to check the response status, header, and body. You can also use assertion libraries likeassertionJwith WebTestClient.

Check out the example below to learn how to run REST API tests using WebTestClient -

Package com.Example.Web client demo;import com.Example.Web client demo.payload.GithubRepo;import com.Example.Web client demo.payload.RepoRequest;import org.claimj.Kern.API.allegations;import org.junit.Jupiter.API.MethodOrderer;import org.junit.Jupiter.API.command;import org.junit.Jupiter.API.Test;import org.junit.Jupiter.API.TestMethodOrder;import org.spring frame.beans.Fabric.annotation.Automatically wired;import org.spring frame.Stiefel.test.context.SpringBootTest;import org.spring frame.http.media type;import org.spring frame.test.network.reactive.Server.WebTestClient;import Reactor.Kern.editor.Mono;@SpringBootTest(webEnvironment= SpringBootTest.web environment.RANDOM_PORT)@TestMethodOrder(MethodOrderer.order note.Class)public Class WebclientDemoApplicationTests {@AutowiredPrivate WebTestClientwebTestClient;@Test@Command(1)public Empty testCreateGithubRepository() {RepoRequestrepoRequest= neu RepoRequest("test-webclient-repository", "Repository created to test WebClient");webTestClient.Post().uri("/api/repos").content type(media type.APPLICATION_JSON).accept(media type.APPLICATION_JSON).Body(Mono.Only(repoRequest), RepoRequest.Class).Exchange().expectedstatus().isOk().expected header().content type(media type.APPLICATION_JSON).expect body().jsonPfad("$.name").is not empty().jsonPfad("$.name").is equal to("test-webclient-repository");}@Test@Command(2)public Empty testGetAllGithubRepositories() {webTestClient.receive().uri("/api/repos").accept(media type.APPLICATION_JSON).Exchange().expectedstatus().isOk().expected header().content type(media type.APPLICATION_JSON).expectedBodyList(GithubRepo.Class);}@Test@Command(3)public Empty testGetSingleGithubRepository() {webTestClient.receive().uri("/api/repos/{repo}", "test-webclient-repository").Exchange().expectedstatus().isOk().expect body().consumeWith(Answer->allegations.claim that(Answer.getResponseBody()).isNotNull());}@Test@Command(4)public Empty testEditGithubRepository() {RepoRequestnewRepoDetails= neu RepoRequest("updated-webclient-repository", "Updated name and description");webTestClient.Patch().uri("/api/repos/{repo}", "test-webclient-repository").content type(media type.APPLICATION_JSON).accept(media type.APPLICATION_JSON).Body(Mono.Only(newRepoDetails), RepoRequest.Class).Exchange().expectedstatus().isOk().expected header().content type(media type.APPLICATION_JSON).expect body().jsonPfad("$.name").is equal to("updated-webclient-repository");}@Test@Command(5)public Empty testDeleteGithubRepository() {webTestClient.extinguish().uri("/api/repos/{repo}", "updated-webclient-repository").Exchange().expectedstatus().isOk();}}

Diploma

Congratulations! In this article, you learned how to work with the Spring 5 reactive WebClient and WebTestClient APIs.

I hope you find the examples presented in this article helpful. You can download the complete example project with all the examples atmein github-repository.

Thank you for reading. I really enjoyed writing this article for you and I hope to see you in my next article soon.

FAQs

What is the use of WebClient and WebTestClient? ›

Interface WebTestClient. Client for testing web servers that uses WebClient internally to perform requests while also providing a fluent API to verify responses. This client can connect to any server over HTTP, or to a WebFlux application via mock request and response objects.

How do I get JSON response from WebClient? ›

You would just use bodyToMono which would return a Mono object. WebClient webClient = WebClient. create(); String responseJson = webClient. get() .

How do I catch exceptions in WebClient? ›

As mentioned in the code block, whenever a 5XX/4XX Error occurs, we can throw a user defined exception, and then execute error handling logic based on those user defined exceptions. Once this error Handler is defined, we can add it in the WebClient Initialisation.

Is WebClient synchronous or asynchronous? ›

Conversely, WebClient is asynchronous and will not block the executing thread while waiting for the response to come back.

Which is better HttpClient or WebClient? ›

WebClient provides a simple but limited wrapper around HttpWebRequest. And HttpClient is the new and improved way of doing HTTP requests and posts, having arrived with . NET Framework 4.5.

What is an example of web client? ›

What are the examples of web clients? These are examples that allow users to perform different tasks using the web. The popular web browsers such as Google Chrome, Internet Explorer, Opera, Firefox and Safari are examples that allow users to access any website through the Internet.

Videos

1. Spring Boot WebFlux | Write Integration Test Using Mockito & Junit | JavaTechie
(Java Techie)
2. JUNIT for Functional EndPoint using WebTestClient
(Code With Dilip)
3. Spring Webclient : Lecture 1 - Perform HTTP GET, POST, PUT, DELETE operations using WebClient
(Code With Dilip)
4. Tutorial #18 | Spring Boot WebClient | Hands on | Spring Webflux | Engineer
(Engineerhoon)
5. Step-By-Step: Testing Reactive REST with @WebFluxTest and WebTestClient
(Tech Recipes)
6. Spring Boot WebFlux | Project setup & understanding Mono & Flux Internal Workflow | JavaTechie
(Java Techie)
Top Articles
Latest Posts
Article information

Author: Arline Emard IV

Last Updated: 01/21/2023

Views: 6413

Rating: 4.1 / 5 (72 voted)

Reviews: 95% of readers found this page helpful

Author information

Name: Arline Emard IV

Birthday: 1996-07-10

Address: 8912 Hintz Shore, West Louie, AZ 69363-0747

Phone: +13454700762376

Job: Administration Technician

Hobby: Paintball, Horseback riding, Cycling, Running, Macrame, Playing musical instruments, Soapmaking

Introduction: My name is Arline Emard IV, I am a cheerful, gorgeous, colorful, joyous, excited, super, inquisitive person who loves writing and wants to share my knowledge and understanding with you.