Skip to main navigation Skip to main content Skip to page footer

Spring - WebClient

| Java Spring Boot

WebClient is a non-blocking, reactive alternative to RestTemplate and is part of Spring WebFlux. It provides a functional programming approach to making HTTP calls and is suitable for reactive programming models.

Configure a simple WebClient Bean

After having declared the required dependency spring-boot-starter-webflux in either our pom.xml or build.gradle. We can add a WebClient bean to our application using its builder.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class WebClientConfig {

    @Bean
    public WebClient webClient(WebClient.Builder builder) {
        return builder.baseUrl("http://localhost:8080/api").build();
    }
}

Using WebClient to Make HTTP Calls

The same way we can make various HTTP calls like GET, POST, PUT, and DELETE using the RestTemplate, we can use the WebClient instance in a service class to make non-blocking, asynchronous HTTP requests to interact with a product service API. It leverages Reactive Programming with Mono and Flux from the Project Reactor library. 

 

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Service
public class ProductServiceClient {

    private final WebClient webClient;

    @Autowired
    public ProductServiceClient(WebClient webClient) {
        this.webClient = webClient;
    }

    // Get All Products (as Mono)
    public Mono<String> getAllProducts() {
        return webClient.get()
                .uri("/products")
                .retrieve()
                .bodyToMono(String.class);
    }

    // Get Product by ID
    public Mono<String> getProductById(int id) {
        return webClient.get()
                .uri("/products/{id}", id)
                .retrieve()
                .bodyToMono(String.class);
    }

    // Create Product
    public Mono<String> createProduct(String product) {
        return webClient.post()
                .uri("/products")
                .bodyValue(product)
                .retrieve()
                .bodyToMono(String.class);
    }

    // Update Product
    public Mono<Void> updateProduct(int id, String updatedProduct) {
        return webClient.put()
                .uri("/products/{id}", id)
                .bodyValue(updatedProduct)
                .retrieve()
                .bodyToMono(Void.class);
    }

    // Delete Product
    public Mono<Void> deleteProduct(int id) {
        return webClient.delete()
                .uri("/products/{id}", id)
                .retrieve()
                .bodyToMono(Void.class);
    }

    // Get All Products as Flux
    public Flux<String> getAllProductsAsFlux() {
        return webClient.get()
                .uri("/products")
                .retrieve()
                .bodyToFlux(String.class);
    }
}

Now there is a lot going on. So we see a new return types Mono and Flux. Mono is a reactive type that represents a single value (or an empty response) that may be available now or in the future. It's the return type of all the methods, allowing for asynchronous and non-blocking execution. Flux is used for example if the product service API supports returning a list of products as a JSON array. Flux<String> means a JSON array of strings.

Next we have the webClient and its method calls using the builder pattern. First we call a method corresponding to the request method like get() or post() then, if necessary we fill the body with bodyValue(), afterwards the retrieve() method executes the HTTP request, followed by what we want to do with the response like bodyToMono(String.class) extracts the response body as a Mono<String>, meaning the response is a single string that will be provided asynchronously.

At the end we have a method using Flux. Overall its usage is the same as with Mono, however we use bodyToFlux(String.class) to parse the response body.

Best Practices for Using WebClient

  • Avoid .block() in Reactive Applications: Use Mono or Flux asynchronously instead of blocking.
  • Error Handling: Use .onStatus() to handle HTTP errors gracefully.
  • Timeouts: Configure timeouts using WebClient.Builder with ExchangeStrategies or custom configurations.
  • Logging: Use logging to debug requests and responses
  • Use Reactive Streams Properly: Combine Mono and Flux with reactive operators like map, flatMap, or filter.
// error handling
webClient.get()
    .uri("/products")
    .retrieve()
    .onStatus(status -> status.is4xxClientError(),
              clientResponse -> Mono.error(new RuntimeException("4xx Error")))
    .onStatus(status -> status.is5xxServerError(),
              clientResponse -> Mono.error(new RuntimeException("5xx Error")))
    .bodyToMono(String.class);

// logging
webClient.get()
    .uri("/products")
    .retrieve()
    .bodyToMono(String.class)
    .doOnNext(response -> System.out.println("Response: " + response));

Advantages of WebClient Over RestTemplate

  1. Non-blocking: Suitable for reactive and high-concurrency applications.
  2. Better Error Handling: Built-in mechanisms for handling HTTP errors.
  3. Supports Streaming: Works seamlessly with streaming data using Flux.
Back