DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

SBOMs are essential to circumventing software supply chain attacks, and they provide visibility into various software components.

Related

  • Enhanced API Security: Fine-Grained Access Control Using OPA and Kong Gateway
  • HTTP API: Key Skills for Smooth Integration and Operation (Part 1)
  • Rate Limiting Strategies for Efficient Traffic Management
  • Unleashing the Power of GPT: A Comprehensive Guide To Implementing OpenAI’s GPT in ReactJS

Trending

  • When MySQL, PostgreSQL, and Oracle Argue: Doris JDBC Catalog Acts as the Peacemaker
  • Memory Leak Due To Mutable Keys in Java Collections
  • How Developers Are Driving Supply Chain Innovation With Modern Tech
  • Tracing Stratoshark’s Roots: From Packet Capture to System Call Analysis
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. Efficient API Communication With Spring WebClient

Efficient API Communication With Spring WebClient

The WebClient is a reactive HTTP client in Spring WebFlux. Its main advantage is asynchronous, non-blocking communication between microservices or external APIs.

By 
Taras Ivashchuk user avatar
Taras Ivashchuk
·
May. 26, 25 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
3.2K Views

Join the DZone community and get the full member experience.

Join For Free

In modern distributed systems, calling other services from an application is a common task for developers. The use of WebClient is ideally suitable for such tasks owing to its non-blocking nature.

The WebClient is a reactive HTTP client in Spring WebFlux. Its main advantage is asynchronic, non-blocking communication between microservices or external APIs. Everything in WebClient is built around events and reactive streams of data, enabling you to focus on business logic and forget about efficient management of threads, locks, or state.

Reactive is everywhere

WebClient Key Features

  • Non-blocking: Provides a non-blocking operation mode that greatly scales up the performance of an application.
  • Reactive REST client: Interact with Spring’s reactive paradigm, using all the benefits for which it is known.
  • Wrapper to Reactor Netty: Since it’s built on top of Reactor Netty, it is a powerful means to work with HTTP at low-level network operations.
  • Immutable, thread-safe: It is safe for multithreaded use.

How WebClient Works

At the core of WebClient is reactive programming, which stands in huge contrast to the classic usage of blocking operations. The main idea behind reactive programming is processing data and events as they appear, without blocking threads. The main mechanism to ensure asynchrony or non-blocking execution of tasks in WebClient is the Event Loop.

How WebClient works


Now, imagine a system that has an incoming request queue — inbound queue — and one thread per CPU core, to keep things simple. This thread constantly polls the queue for tasks. In case there are no tasks in the queue, it stays in an IDLE state. Once a request comes into the queue, this thread picks it up for execution but does not wait until the task is finished. Right away, it starts processing another request. If it is completed, then it fetches the response and puts it into an outbound queue for further processing.

Key Aspects of Working With WebClient

Creating a WebClient

A simple example of creating a WebClient:

Java
 
@Bean(name = "webClient")
public WebClient commonWebClient() {
        return WebClient.builder()
                .baseUrl("http://localhost:8080")
                .build();
}


Path Variables

In WebClient you have several ways to work with URI variables are supported, allowing you to flexibly build request URLs.

String concatenation: The most evident and, hence, the easiest way is to use string concatenation. In real programs, it is seldom applied.

Java
 
public Mono<Product> commonOption(Long id) {
    return webClient.get()
            .uri("api/products/" + id)
            .retrieve()
            .bodyToMono(Product.class);
}


Using varargs: This is a variable-length method that lets you dynamically replace values in the URI template using variables, with positional substitution, meaning it’s order-dependent. You must pass the variables in the same order they appear in the URI template.

Java
 
public Mono<Product> varargOption(Long id) {
    return webClient.get()
            .uri("api/products/{id}", id)
            .retrieve()
            .bodyToMono(Product.class);
}

public Mono<Product> varargsOption(String version, Long id) {
    return webClient.get()
            .uri("api/{version}/products/{id}", version, id)
            .retrieve()
            .bodyToMono(Product.class);
}


Using a map: In case your URL contains many variables or their order may change, using a Map becomes the most convenient way to work with URI variables.

Java
 
public Mono<Product> mapOption(String version, Long id) {
    var map = Map.ofEntries(
            Map.entry("version", version),
            Map.entry("id", id)
    );

    return webClient.get()
            .uri("api/{version}/products/{id}", map)
            .retrieve()
            .bodyToMono(Product.class);
}


Query Parameters

When you’re dealing with HTTP requests, in real-life situations like using WebClient to interact with a server, you often need to include query parameters in the URL, such as filters for data retrieval and search criteria for results. These parameters play a role in informing the server on how to handle your request. In our exploration of working with WebClient, we will delve into methods of incorporating query parameters into a URL, which include using UriBuilder passing parameters through a Map structure and understanding how it manages encoding and value processing nuances.

Using UriBuilder

The UriBuilder method provides a safe and flexible way to create URLs with query parameters. It allows you to dynamically append paths, parameters, and variables, without needing to worry about encoding or formatting issues.

Java
 
Flux<Product> products = webClient.get()
    .uri(uriBuilder -> 
      uriBuilder
         .path("/api/products")
         .queryParam("page", 0)
         .queryParam("size", 20)
        .build())
    .retrieve()
    .bodyToFlux(Product.class);


Named Parameters

If you want to work with named parameters, you can use the build(Map<String, ?> variables) method.

Java
 
Map<String, Object> uriVariables = Map.of("f1", 0, "f2", 20);

Flux<Product> products = webClient.get()
    .uri(uriBuilder -> 
      uriBuilder
         .path("/api/products")
         .queryParam("page", "{f1}")
         .queryParam("size", "{f2}")
        .build(uriVariables))
    .retrieve()
    .bodyToFlux(Product.class);


Sending Parameters via Map

Another approach is passing query parameters using a MultiValueMap.

Java
 
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("page", "10");
queryParams.add("size", "20");
queryParams.add("filter", "active");

Flux<Product> products = webClient.get()
    .uri(uriBuilder -> 
      uriBuilder
         .path("/api/products")
         .queryParams(queryParams)
        .build())
    .retrieve()
    .bodyToFlux(Product.class);


Sending Multiple Values

Java
 
queryParams.add("category", "electronics");
queryParams.add("category", "books");


This will generate the parameters category=electronics&category=books.

Other Useful UriBuilder Methods

1. Conditional parameter addition

You can choose to include certain parameters only when specific conditions are met.

Java
 
String filter = getFilter(); // May return null

Flux<Product> products = webClient.get()
    .uri(uriBuilder -> {
        var builder = uriBuilder.path("/api/products");
        if (filter != null) {
            builder.queryParam("filter", filter);
        }
        return builder.build();
    })
    .retrieve()
    .bodyToFlux(Product.class);


2. Passing collections

You can pass multiple values for a single parameter by using a list.

Java
 
var categories = List.of("electronics", "books", "clothing");

Flux<Product> products = webClient.get()
    .uri(uriBuilder -> uriBuilder
        .path("/api/products")
        .queryParam("category", categories)
        .build())
    .retrieve()
    .bodyToFlux(Product.class);


This will generate the URL query parameters like category=electronics&category=books&category=clothing.

Streaming Response (Flux)

In certain situations, you may need to start processing data as soon as it begins arriving, rather than waiting for all of it to load at once. This can be especially useful when working with large datasets or continuous data streams. WebClient has built-in support for streaming responses using reactive types like Flux

Java
 
public Flux<Product> streamingProduct() {
    return webClient.get()
            .uri("api/products")
            .retrieve()
            .bodyToFlux(Product.class);
}


Body Publisher vs. Body Value

When working with WebClient, you’ll frequently need to send POST requests that include a body. Depending on the type of data you’re handling, you’ll want to choose between the bodyValue() and body() methods. Understanding when to use each will help you make the most of WebClient and ensure your data gets transmitted properly.

bodyValue()

When you use the bodyValue() method, it’s best for situations where you have an object or value stored in memory that you want to send as the request body. This could be as straightforward as a string or a number, or it could involve an intricate object like a DTO that can be smoothly converted into formats like JSON.

Java
 
public Mono<Product> bodyValue() {
    var product = new Product(1L, "product_1");

    return webClient
            .post()
            .uri("api/products")
            .bodyValue(product) // <-- this is for objects
            .retrieve()
            .bodyToMono(Product.class);
}


When Should You Use bodyValue()

  • Data is ready: Use this method when your data is already available in memory and doesn’t need to be fetched asynchronously.
  • Simple data types: It’s perfect for sending basic data types like strings, numbers, or DTOs.
  • Synchronous scenarios: When there’s no need for asynchronous or streaming data, bodyValue() keeps things straightforward.

body()

The body() function, on the other hand, is intended for more complicated scenarios. When your data originates from a reactive stream or any other source that uses the Publisher interface, such as Flux or Mono, this technique can be helpful. It comes in particularly useful when you’re retrieving data from a database or other service, for example, and the data you’re transmitting isn’t immediately available.

Java
 
public Mono<Product> bodyMono(Mono<Product> productMono) {
    return webClient
            .post()
            .uri("api/products")
            .body(productMono, Product.class) // <-- Use this for Mono/Flux data streams
            .retrieve()
            .bodyToMono(Product.class);
}


When dealing with this scenario, we are providing a Mono<Product> to the body() function. The WebClient will wait for the Mono to complete before sending the resolved data in the request body.

Default Headers

During development work, it’s often necessary to use HTTP headers for all requests — whether for authentication purposes, to specify content type, or to send custom metadata. Instead of including these headers in every single request separately, WebClient offers the option to define default headers at the client level.
When you first create your WebClient instance make sure to set up these headers so they are automatically included in all requests, saving time and reducing the need, for code.

Using defaultHeader

If you only need to set a few headers that will apply to all requests made by your WebClient, the defaultHeader method is a straightforward solution.

Java
 
public WebClient commonWebClient() {
        return WebClient.builder()
                .baseUrl("http://localhost:8080")
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .defaultHeader("Custom-Header", "Custom-Value")
                .build();
}


Using defaultHeaders

For situations where you need to set multiple headers at once, you can use the defaultHeaders method, which accepts a Consumer<HttpHeaders>. This approach lets you add several headers in a more organized way.

Java
 
public WebClient commonWebClient() {
        return WebClient.builder()
                .baseUrl("http://localhost:8080")
                .defaultHeaders(headers -> {
                    headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
                    headers.add("Custom-Header", "Custom-Value");
                })
                .build();
}


You can also use a map with headers and the setAll method to set them all at once.

Java
 
public WebClient commonWebClient() {
        var headersMap = Map.of(
                HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE,
                "Custom-Header", "Custom-Value"
        );

        return WebClient.builder()
                .baseUrl("http://localhost:8080")
                .defaultHeaders(headers -> headers.setAll(headersMap))
                .build();
}


Overriding Headers for a Specific Request

Sometimes, there could be situations where you have to change or add headers for a request. In scenarios when dealing with a single request level usage of header() or headers() directly.

Java
 
public Flux<Product> headers() {
        return webClient.get()
                .uri("api/products")
                .header("Custom-Header", "New-Value") //change header
                .headers(httpHeaders -> {
                    httpHeaders.add("Additional-Header", "Header-Value"); //add new header
                })
                .retrieve()
                .bodyToFlux(Product.class);
}


Setting Authentication Headers

Authentication headers are essential when dealing with secured endpoints. Whether you’re using Basic Authentication or a Bearer token, WebClient makes it easy to set these headers.

Basic Authentication

For Basic Authentication, where credentials are encoded in Base64, you can use the setBasicAuth() method:

Java
 
public WebClient commonWebClient() {
    return WebClient.builder()
            .baseUrl("http://localhost:8080")
            .defaultHeaders(headers -> headers.setBasicAuth("username", "password"))
            .build();
}


Bearer Token Authentication

If you are using Bearer tokens, such as JWTs, the setBearerAuth() method simplifies the process:

Java
 
public WebClient commonWebClient() {
    return WebClient.builder()
            .baseUrl("http://localhost:8080")
            .defaultHeaders(headers -> headers.setBearerAuth("your_jwt_token"))
            .build();
}


Dynamically Updating Authentication Tokens

In cases where your authentication token may change frequently (e.g., OAuth2 tokens), it’s efficient to dynamically set these headers for each request. This can be accomplished using an ExchangeFilterFunction. Here's an example of setting up a dynamic Bearer token.

Java
 
@Bean(name = "webClientDynamicAuth")
public WebClient webClientDynamicAuth() {
        ExchangeFilterFunction authFilter = ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
            ClientRequest authorizedRequest = ClientRequest.from(clientRequest)
                    .headers(headers -> headers.setBearerAuth(getCurrentToken()))
                    .build();
            return Mono.just(authorizedRequest);
        });

        return WebClient.builder()
                .baseUrl("http://localhost:8080")
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .filter(authFilter)
                .build();
}


In this case, the getCurrentToken() method fetches the latest token each time a request is initiated to guarantee that the authentication token remains current at all times.

Error Handling

When you use WebClient to connect to remote services, errors such as network problems, service outages, and server or client-side failures can occur. It’s important to handle these errors so that your application remains stable and resilient. In this part, we will look at ways to handle errors in WebClient and focus on operators such as onErrorReturn, onErrorResume, doOnError , and onErrorMap.

Key Error-Handling Methods

These are special operators in reactive programming that help in controlling and handling errors (exceptions) in a thread, which is a clean and efficient way of handling exceptions.

onErrorReturn

The onErrorReturn method replaces the error in the thread with the default value, and makes sure that the thread terminates without throwing an exception.

Return a default value on any error:

Java
 
Mono<Product> productMono = webClient.get()
    .uri("/api/products/{id}", id)
    .retrieve()
    .bodyToMono(Product.class)
    .onErrorReturn(new Product()); // Returns an empty product on any error


In this scenario, if an error occurs, a default Product object is returned, preventing an exception from propagating.

Handling a specific type of error:

Java
 
Mono<Product> productMono = webClient.get()
    .uri("/api/products/{id}", id)
    .retrieve()
    .bodyToMono(Product.class)
    .onErrorReturn(WebClientResponseException.NotFound.class, new Product(-1, "NOT_FOUND"));


Here, a 404 Not Found error results in a custom Product object, while other errors will continue to be passed through the stream.

Filter errors using a predicate:

Java
 
Mono<Product> productMono = webClient.get()
    .uri("/api/products/{id}", id)
    .retrieve()
    .bodyToMono(Product.class)
    .onErrorReturn(
        e -> e instanceof WebClientResponseException && ((WebClientResponseException) e).getStatusCode() == HttpStatus.SERVICE_UNAVAILABLE,
        new Product(-1, "UNAVAILABLE")
    );


In this example, we use a predicate to check if the error is a 503 Service Unavailable status, and in that case, return an object.

onErrorResume

The onErrorResume method allows you to switch the stream to an alternative Publisher in case of an error. This gives you the opportunity to perform additional actions or return another data stream when an exception occurs.

Switch to an alternative stream on any error:

Java
 
Mono<Product> productMono = webClient.get()
    .uri("/api/products/{id}", id)
    .retrieve()
    .bodyToMono(Product.class)
    .onErrorResume(e -> {
        // Log the error
        logger.error("Error retrieving product", e);
        // Return an alternative Mono
        return Mono.just(new Product());
    });


Handling a specific type of error:

Java
 
Mono<Product> productMono = webClient.get()
    .uri("/api/products/{id}", id)
    .retrieve()
    .bodyToMono(Product.class)
    .onErrorResume(WebClientResponseException.NotFound.class, e -> {
        // Additional handling of 404 error
        logger.warn("Product not found: {}", id);
        // Return an empty Mono
        return Mono.empty();
    });


Using a predicate:

Java
 
Mono<Product> productMono = webClient.get()
    .uri("/api/products/{id}", id)
    .retrieve()
    .bodyToMono(Product.class)
    .onErrorResume(e -> e instanceof TimeoutException, e -> {
        // Handle timeout
        return getProductFromCache(id); // Method to get product from cache
    });


Here, if a timeout occurs, the error is handled by falling back to a cached version of the product.

doOnError

The doOnError method allows you to perform side effects (such as logging) when an error occurs, without changing the error stream itself.

Logging any error:

Java
 
Mono<Product> productMono = webClient.get()
    .uri("/api/products/{id}", id)
    .retrieve()
    .bodyToMono(Product.class)
    .doOnError(e -> logger.error("Error retrieving product", e));


Logging a specific error:

Java
 
Mono<Product> productMono = webClient.get()
    .uri("/api/products/{id}", id)
    .retrieve()
    .bodyToMono(Product.class)
    .doOnError(WebClientResponseException.NotFound.class, e -> logger.warn("Product not found: {}", id));


Collecting error metrics:

Java
 
Mono<Product> productMono = webClient.get()
    .uri("/api/products/{id}", id)
    .retrieve()
    .bodyToMono(Product.class)
    .doOnError(e -> errorCounter.increment());


onErrorMap

This method transforms one type of exception into another. It is helpful when you need to mask internal details or convert exceptions into custom ones.

Transform all errors into a custom exception:

Java
 
Mono<Product> productMono = webClient.get()
    .uri("/api/products/{id}", id)
    .retrieve()
    .bodyToMono(Product.class)
    .onErrorMap(e -> new ProductServiceException("Error retrieving product", e));


Transform a specific error:

Java
 
Mono<Product> productMono = webClient.get()
    .uri("/api/products/{id}", id)
    .retrieve()
    .bodyToMono(Product.class)
    .onErrorMap(WebClientResponseException.NotFound.class, e -> new ProductNotFoundException("Product not found: " + id));


Combining Error Handling Methods

You can combine various methods for more flexible error handling.

Java
 
Mono<Product> productMono = webClient.get()
    .uri("/api/products/{id}", id)
    .retrieve()
    .bodyToMono(Product.class)
    .doOnError(e -> logger.error("Error retrieving product", e))
    .onErrorResume(WebClientResponseException.NotFound.class, e -> {
        logger.warn("Product not found: {}", id);
        return Mono.empty();
    })
    .onErrorReturn(new Product(-99, "DEFAULT"));


In this case:

  • All errors are logged.
  • A 404 Not Found error returns an empty Mono.
  • For any other error, a default Product object is returned to ensure the stream completes without propagating the error.

retrieve() vs. exchange()

When you work with WebClient to send HTTP requests. You may come across two approaches, for managing responses: retrieve() and exchange().

retrieve()

The approach makes it easier to deal with replies by concentrating on getting the response content and managing HTTP error statuses automatically.

Characteristics of retrieve()

  • Automatic error handling: In case the server encounters an error (status codes 4xx or 5xx), retrieve() will automatically throws a WebClientResponseException.
  • Ease of use: When you just need only the body of a response and do not even care about formats like headers or status codes.
Java
 
Mono<Product> productMono = webClient.get()
    .uri("/api/products/{id}", id)
    .retrieve()
    .bodyToMono(Product.class);


exchange()/exchangeToMono()

Returns ClientResponse that provides access to the underlying status code and response from the server. With that, you are able to get into details like status codes, headers, cookies, etc.

Note: As of Spring WebFlux 5.2, the exchange() method is deprecated; use the safer and preferred exchangeToMono() or exchangeToFlux()

Characteristics of exchangeToMono() and exchangeToFlux

  • Full control: You can manipulate the ClientResponse object, which allows you to deal with headers, status codes, cookies, and everything else.
  • Flexibility in error handling: Implement your own response status and exceptions management strategy with the same interface.
Java
 
Mono<Product> productMono = webClient.get()
    .uri("/api/products/{id}", id)
    .exchangeToMono(response -> {
        HttpStatus status = response.statusCode();
        HttpHeaders headers = response.headers().asHttpHeaders();

        if (status.is2xxSuccessful()) {
            // Extract custom header
            String requestId = headers.getFirst("X-Request-ID");
            return response.bodyToMono(Product.class)
                .doOnNext(product -> {
                    // Use information from headers
                    product.setRequestId(requestId);
                });
        } else {
            // Handle errors
            return response.createException().flatMap(Mono::error);
        }
    });


In this example:

  • The status code and headers are retrieved from the ClientResponse.
  • If the response is successful, we extract the X-Request-ID custom header and add it to the Product object.
  • If an error occurs, we handle it by creating and propagating an exception.

Detailed Comparison of retrieve() and exchangeToMono()

retrieve() vs. exchangeToMono()


ExchangeFilterFunction

ExchangeFilterFunction is a functional interface in Spring WebFlux, that gives you the ability to intercept and modify requests and responses. For example, features like logging, authentication, error handling, metrics, and caching can be plugged into your HTTP calls with the help of this ability without altering the core application logic.

Serving as a filter, ExchangeFilterFunction represents a web filter that runs on every request and response. You can chain multiple filters together, offering a modular and flexible way to enhance the functionality of your HTTP client.

Java
 
@FunctionalInterface
public interface ExchangeFilterFunction {
    Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next);
}


It accepts a ClientRequest and an ExchangeFunction, and returns a Mono<ClientResponse>. In simple terms, it is a function that enables you to:

  • Modify the Request before it’s sent.
  • Modify the Response after it’s received.
  • Perform Additional Actions before or after sending the request and receiving the response.

How to Use ExchangeFilterFunction

Adding Filters When Creating WebClient

When constructing a WebClient instance with the builder, you can tack one or more filters onto it like so.

Java
 
public WebClient commonWebClient() {
        return WebClient.builder()
                .baseUrl("http://localhost:8080")
                .filter(filter1)
                .filter(filter2)
                .build();


Note: Filters are applied in the order they are added.

Creating an ExchangeFilterFunction

You can use static methods exposed by ExchangeFilterFunction:

  • ofRequestProcessor(): To process or modify the ClientRequest
  • ofResponseProcessor(): To process or modify the ClientResponse

Example 1: Logging requests and responses

Java
 
public WebClient commonWebClient() {
    ExchangeFilterFunction logRequest = ExchangeFilterFunction.ofRequestProcessor(request -> {
        logger.info("Request: {} {}", request.method(), request.url());
        return Mono.just(request);
    });

    ExchangeFilterFunction logResponse = ExchangeFilterFunction.ofResponseProcessor(response -> {
        logger.info("Response status: {}", response.statusCode());
        return Mono.just(response);
    });

    return WebClient.builder()
            .baseUrl("http://localhost:8080")
            .filter(logRequest)
            .filter(logResponse)
            .build();
}


Example 2: Error handling and retrying

Java
 
public WebClient commonWebClient() {
    ExchangeFilterFunction retryFilter = (request, next) -> next.exchange(request)
            .retryWhen(Retry.fixedDelay(3, Duration.ofSeconds(1))
            .filter(throwable -> throwable instanceof IOException))
            .onErrorResume(throwable -> {
                logger.error("Error executing request: {}", throwable.getMessage());
                return Mono.error(throwable);
            });

    return WebClient.builder()
            .baseUrl("http://localhost:8080")
            .filter(retryFilter)
            .build();
}


  • The filter intercepts the response and, if an IOException occurs, tries to repeat the request up to three times with a delay of 1 second.
  • On final failure, it logs the error and propagates the exception.

Conclusion

During this article, we saw some of the features of the Spring WebClient and how this is a better approach provided by Spring to do HTTP client requests in a reactive and non-blocking way. We deeply reviewed the powerful features regarding setup and configurations. We have reviewed some of the benefits that it offers at the time of composing requests and handling responses. In the same vein, we likewise discussed handling responses coming from a service, handling error scenarios, and how to handle an empty response.

You can also read an article about functional endpoints.

API RETRIEVE Requests

Opinions expressed by DZone contributors are their own.

Related

  • Enhanced API Security: Fine-Grained Access Control Using OPA and Kong Gateway
  • HTTP API: Key Skills for Smooth Integration and Operation (Part 1)
  • Rate Limiting Strategies for Efficient Traffic Management
  • Unleashing the Power of GPT: A Comprehensive Guide To Implementing OpenAI’s GPT in ReactJS

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: