In this post, we will talk about the use of WebClient in Spring 5. This feature was introduced some time ago and is gradually replacing RestTemplate.
What is WebClient in Spring 5?
This module of Spring has been created as part of the Spring Web Reactive module and aims to replace RestTemplate for these cases. WebClient has been designed to be reactive, non-blocking, and to work over HTTP/1.1.
In summary, WebClient is a Spring class that allows you to make reactive and non-blocking HTTP requests in Spring-based applications. It is a recommended option for new applications and gradually replaces RestTemplate in the Spring ecosystem.
Why use WebClient?
- Asynchronous and Non-Blocking: WebClient is built on the reactive programming model provided by Spring WebFlux. It allows you to perform non-blocking and asynchronous HTTP requests, which can improve the performance and scalability of your application, especially in scenarios with high concurrency.
- Functional Programming Style: WebClient provides a functional and fluent API for constructing HTTP requests. It allows you to chain operations and apply transformations to the request/response, making it more expressive and easier to work with, especially for complex scenarios.
- Built-in Support for Reactive Streams: WebClient integrates seamlessly with reactive streams, allowing you to process data streams in a reactive manner. It can handle large response bodies efficiently by streaming the data instead of loading it entirely into memory.
- Extensible and Customizable: WebClient offers various extension points and configuration options to adapt to your specific requirements. You can customize aspects such as timeouts, connection pooling, request/response interception, and error handling.
- Integration with Spring Ecosystem: WebClient is part of the Spring ecosystem and integrates well with other Spring projects like Spring Boot and Spring Cloud. It plays nicely with features such as dependency injection, auto-configuration, and testing frameworks, making it a convenient choice for Spring-based applications.
Overall, WebClient provides a modern and efficient approach to making RESTful API requests, particularly in reactive and asynchronous applications, and offers flexibility and integration within the Spring ecosystem.
Maven Dependencies to Configure WebClient in Spring 5
To be able to import the necessary libraries, we need to add the following dependencies to our pom.xml file.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.projectreactor</groupId> <artifactId>reactor-spring</artifactId> <version>${reactor-version}</version> </dependency>
Creating a WebClient
We can create a WebClient by either creating an instance and passing a URI to the constructor when the instance is created, or by creating the instance using the DefaultWebClientBuilder class to pass different parameters.
WebClient client = WebClient.create(); WebClient client = WebClient.create("http://localhost:8080"); WebClient client = WebClient.builder() .baseUrl("http://localhost:8080") .defaultCookie("cookieKey", "cookieValue") .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080")).build();
Configuring a WebClient
By default, HTTP has a timeout of 30 seconds, but in many cases, it is necessary to increase it due to requirements or needs. To do this, we can use the TCPClient class and the ReadTimeoutHandler and WriteTimeoutHandler to set the desired value:
TcpClient tcpClient = TcpClient .create() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15000) .doOnConnected(connection -> { connection.addHandlerLast(new ReadTimeoutHandler(15000, TimeUnit.MILLISECONDS)); connection.addHandlerLast(new WriteTimeoutHandler(15000, TimeUnit.MILLISECONDS)); }); WebClient client = WebClient.builder() .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient))) .build();
Performing an HTTP Request with WebClient
The first thing we are going to do is define our HTTP request, with its verb and URI.
WebClient.UriSpec<WebClient.RequestBodySpec> post = client.method(HttpMethod.POST); //Le proporcionamos la uri WebClient.RequestBodySpec uri = client .method(HttpMethod.POST) .uri("/data");
We can also pass a body in the request; for example, in a Post call, it would be useful to pass an object as the body:
WebClient.RequestHeadersSpec<?> requestSpec2 = WebClient .create("http://localhost:8080") .post() .uri(URI.create("/resource")) .body(BodyInserters.fromObject("data"));
The class used above, BodyInserters, is an interface responsible for populating a ReactiveHttpOutputMessage during insertion.
Furthermore, as we do with other clients, we can add headers such as “If-None-Match”, “If-Modified-Since”, “Accept”, and “Accept-Charset”.
WebClient.ResponseSpec response = uri .body(inserter3) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .accept(MediaType.APPLICATION_JSON) .acceptCharset(Charset.forName("UTF-8")) .ifNoneMatch("*") .retrieve();
Getting a Response with WebClient
The received response can be obtained using the exchange or retrieve methods. Personally, I prefer retrieve as it is faster for directly obtaining the response body. On the other hand, the exchange method provides a ClientResponse object with headers and status. Below is an example of using retrieve.
String res = request .retrieve() .bodyToMono(String.class) .block();
With the bodyToMono method, we will throw a WebClientException if we encounter a 4XX or 5XX error.
Testing WebClient with WebTestClient
With the introduction of WebFlux in Spring, WebTestClient has become the go-to tool for testing these types of endpoints. It has a very similar API to WebClient.
The client for testing can be linked to a real server or work with specific controllers or functions. To complete end-to-end integration testing with actual requests, we can use the bindToServer method.
WebTestClient webTestClient = WebTestClient.bindToServer().baseUrl("http://localhost:8080").build();
We can test a RouterFunction by passing it to the bindToRouterFunction method.
RouterFunction routerFunction = RouterFunctions.route( RequestPredicates.GET("/data"), request -> ServerResponse.ok().build() ); WebTestClient .bindToRouterFunction(routerFunction) .build().get().uri("/data") .exchange() .expectStatus().isOk() .expectBody().isEmpty();
Something quite interesting that has been introduced is when we use bindToApplicationContext. It takes the ApplicationContext, performs bean control, and configures WebFlux, in other words, it uses @EnableWebFlux:
@Autowiredprivate ApplicationContext context; WebTestClient testClient = WebTestClient.bindToApplicationContext(context).build();
Another way to do it, which is faster, is by providing a list of controllers instead of using bindToApplicationContext. We can use bindToController and inject the controller.
Once we have created the WebTestClient, the rest of the operations would be similar to those performed with WebClient. Here is an example of the WebTestClient object:
WebTestClient .bindToServer() .baseUrl("http://localhost:8080") .build() .post() .uri("/resource") .exchange() .expectStatus() .isCreated() .expectHeader() .valueEquals("Content-Type", "application/json").expectBody().isEmpty();
Conclusion
In this post about use of WebClient in Spring we have seen how this Client allow you to make reactive and non-blocking HTTP requests in Spring-based applications.
If you need more information, you can leave us a comment or send an email to refactorizando.web@gmail.com You can also contact us through our social media channels on Facebook or twitter and we will be happy to assist you!!