StackTips
 14 minutes

Calling HTTP Services with WebClient in Spring Boot

By Nilanchala @nilan, On Mar 25, 2024 Spring Boot 4.63K Views

WebClient is a non-blocking, reactive HTTP client introduced in Spring 5.0, which is the reactive counterpart to the traditional RestTemplate in Spring Boot. It provides a simplified and intuitive API for making HTTP requests. It is designed to handle both synchronous and asynchronous operations.

In this article we will see how to use WebClient to integrate with external HTTP/REST services from Spring Boot.

To use WebClient in a Spring Boot application, follow these steps:

Adding WebClient Dependency

Ensure you have the necessary dependencies in your project. If you're using Maven, add the following dependency to your pom.xml file:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

Create a WebClient Bean 

In your code, create an instance of WebClient using the WebClient.builder() method. You can customize the WebClient instance by setting properties such as timeouts, authentication, SSL configuration, and more.

WebClient webClient = WebClient.builder()
    .baseUrl("https://api.example.com")
    .build();

Making HTTP Request using WebClient 

Use the methods provided by the WebClient instance to make HTTP requests. WebClient offers methods like get(), post(), put(), delete(), etc., to perform different types of requests. You can chain these methods together to customize the request.

Mono<MyResponse> responseMono = webClient.get()
    .uri("/data/{id}", id)
    .retrieve()
    .bodyToMono(MyResponse.class);

Using WebClient in Spring Boot

Let us now examine, how to use WebClient in real-time to consume the following APIs from the Spring Boot application.

GET
https://fakestoreapi.com/products - get all products
https://fakestoreapi.com/products/1 - get specific product based on id

POST
https://fakestoreapi.com/products - add a new product

DELETE
https://fakestoreapi.com/products/1 - get specific product based on id

Let us now declare a domain model for the Product object.

public class Product {

    private String image;
    private double price;
    private String description;
    private int id;
    private String title;
    private String category;

    //Getter, Setter
}

Consuming GET API in WebClient

Let us now create a service class ProductService that consumes the Fakestore API endpoints using WebClient.

@Service
public class ProductService {

    private final WebClient webClient;

    public ProductService() {
        // you may configure the base URL in applcation.properties 
        this.webClient = WebClient.create("https://fakestoreapi.com");
    }

    public Mono<Product[]> getProducts() {
        return webClient.get()
                .uri("/products")
                .retrieve()
                .bodyToMono(Product[].class);
    }

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

The getProducts() method uses WebClient to make a GET request to the /products endpoint. The retrieve() method initiates the request and returns a ClientResponse object. The bodyToMono() method is then used to deserialize the response body into an array of Product objects. Finally, the result is returned as a Mono<Product[]>.

Now we can consume the ProductService and call getProducts() endpoint to return the list of products as follows:

Mono<Product[]> productsMono = productService.getProducts();
productsMono.subscribe(
        products -> {
            //TODO write your logic to handle list of products repose
        },
        error -> {
            // Handle error cases
            error.printStackTrace();
        }
);

Similarly. we can call getProductById() method to return a product specified by Id.

Mono<Product> productMono = productService.getProductById(1);
productMono.subscribe(
        product -> {
            //TODO write your logic to handle product response
        },
        error -> {
            // Handle error cases
            error.printStackTrace();
        }
);

Consuming POST API in WebClient

Now let us expand the ProductService class to add support for the addProduct() method. This method makes a POST request to the /products endpoint and sends the product information as the request body.

public Mono<Product> addProduct(Product product) {
    return webClient.post()
            .uri("/products")
            .contentType(MediaType.APPLICATION_JSON)
            .body(BodyInserters.fromValue(product))
            .retrieve()
            .bodyToMono(Product.class);
}

The post() method is used to initiate the POST request, and the body() method with BodyInserters.fromValue() is used to set the request body with the provided product object. The contentType() method sets the request content type to application/json to indicate that the request body is in JSON format.

Now, to consume this new endpoint as follows:

Product product = new Product();
product.setTitle("test product");
product.setPrice(13.5);
product.setDescription("lorem ipsum set");
product.setImage("https://i.pravatar.cc");
product.setCategory("electronic");
Mono<Product> addedProductMono = productService.addProduct(product);
addedProductMono.subscribe(
        addedProduct -> {
            //TODO Added product 
        },
        error -> {
            // Handle error cases
            error.printStackTrace();
        }
);

Consuming DELETE API in WebClient

Now to delete a product, add the following deleteProduct() method to the ProductService.

public Mono<Void> deleteProduct(int productId) {
    return webClient.method(HttpMethod.DELETE)
            .uri("/products/{id}", productId)
            .retrieve()
            .bodyToMono(Void.class);
}

To consume this new endpoint and delete a product, you can use the ProductService as follows:

Mono<Void> deletedProductMono = productService.deleteProduct(productId);
deletedProductMono.subscribe(
        response -> {
            // TODO Product deleted successfully
        },
        error -> {
            // Handle error cases
            error.printStackTrace();
        }
);

Implement Basic Authentication in WebClient

To implement basic authentication with WebClient, we need to create a custom interceptor class that implements the ExchangeFilterFunction interface. The ExchangeFilterFunction interface represents a functional contract for intercepting and modifying the exchange between the WebClient and the remote server during an HTTP request/response cycle.

It allows applying custom logic such as modifying request headers, logging, adding authentication tokens, modifying the request body, handling retries, or performing custom transformations on the response.

Let us create a BasicAuthInterceptor class that implements the ExchangeFilterFunction interface.

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import reactor.core.publisher.Mono;

class BasicAuthInterceptor implements ExchangeFilterFunction {
    private final String username;
    private final String password;

    public BasicAuthenticationInterceptor(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @Override
    public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
        HttpHeaders headers = request.headers();
        headers.setBasicAuth(username, password);
        headers.setContentType(MediaType.APPLICATION_JSON);
        return next.exchange(request);
    }
}

Here's an example of how you can configure WebClient to use the Interceptor for doing the basic authentication:

this.webClient = WebClient.builder()
        .baseUrl("https://fakestoreapi.com")
        .filter(new BasicAuthInterceptor("username", "password"))
        .build();

With this configuration, WebClient will automatically include the basic authentication credentials in the request headers for all requests made using that WebClient instance.

Passsing X-API-Key Header in WebClient Request

To pass an X-API-Key header in WebClient requests in Spring Boot, you can use the header() method provided by the WebClient's mutate() function. Here's an example of how you can include the X-API-Key header in WebClient requests:

WebClient webClient = WebClient.builder()
        .baseUrl("https://fakestoreapi.com")
        .build();

 return webClient.get()
        .uri("/products")
        .header(HttpHeaders.AUTHORIZATION, "X-API-Key: YOUR_API_KEY")
        .retrieve()
        .bodyToMono(Product[].class);

nilan avtar

Nilanchala

I'm a blogger, educator and a full stack developer. Mainly focused on Java, Spring and Micro-service architecture. I love to learn, code, make and break things.