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

Related

  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)
  • How To Build Web Service Using Spring Boot 2.x
  • Why Queues Don’t Fix Scaling Problems
  • How to Identify the Underlying Causes of Connection Timeout Errors for MongoDB With Java

Trending

  • OpenAPI From Code With Spring and Java: A Recipe for Your CI
  • From Indicators to Insights: Automating IOC Enrichment Using Python and Threat Feeds
  • LLM-Powered Deep Parsing for Industrial Inventory Search
  • Zero-Downtime Deployments for Java Apps on Kubernetes
  1. DZone
  2. Coding
  3. Java
  4. Spring WebFlux Retries

Spring WebFlux Retries

If you use Spring WebFlux, you probably want your requests to be more resilient. Here, learn to use the retries that come packaged with the WebFlux library.

By 
Emmanouil Gkatziouras user avatar
Emmanouil Gkatziouras
DZone Core CORE ·
Sep. 16, 23 · Tutorial
Likes (28)
Comment
Save
Tweet
Share
8.1K Views

Join the DZone community and get the full member experience.

Join For Free

If you use Spring WebFlux, you probably want your requests to be more resilient. In this case, we can just use the retries that come packaged with the WebFlux library.

WebFlux logoThere are various cases that we can take into account:

  • Too many requests to the server
  • An internal server error
  • Unexpected format
  • Server timeout

We would make a test case for those using MockWebServer.

We will add the WebFlux and the MockWebServer to a project:

XML
 
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
    <version>2.7.15</version>
</dependency>
 
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>mockwebserver</artifactId>
    <version>4.11.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-test</artifactId>
    <scope>test</scope>
    <version>3.5.9</version>
</dependency>


Let’s check the scenario of too many requests on the server. In this scenario, our request fails because the server will not fulfill it. The server is still functional however and on another request, chances are we shall receive a proper response.

Java
 
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.SocketPolicy;
import org.junit.jupiter.api.Test;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
 
import java.io.IOException;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
 
class WebFluxRetry {
 
    @Test
    void testTooManyRequests() throws IOException {
        MockWebServer server = new MockWebServer();
        MockResponse tooManyRequests = new MockResponse()
                .setBody("Too Many Requests")
                .setResponseCode(429);
        MockResponse successfulRequests = new MockResponse()
                .setBody("successful");
 
        server.enqueue(tooManyRequests);
        server.enqueue(tooManyRequests);
        server.enqueue(successfulRequests);
        server.start();
 
        WebClient webClient = WebClient.builder()
                .baseUrl("http://" + server.getHostName() + ":" + server.getPort())
                .build();
 
        Mono<String> result = webClient.get()
                .retrieve()
                .bodyToMono(String.class)
                .retry(2);
 
        StepVerifier.create(result)
                .expectNextMatches(s -> s.equals("successful"))
                .verifyComplete();
 
        server.shutdown();
    }
}


We used the mock server in order to enqueue requests. Essentially the requests we placed on the mock server will be enqueued and consumed every time we do a request. The first two responses would be failed 429 responses from the server.

Let’s check the case of 5xx responses. A 5xx can be caused by various reasons. Usually, if we face a 5xx, there is probably a problem in the server codebase. However, in some cases, 5xx might come as a result of an unstable service that regularly restarts. Also, a server might be deployed in an availability zone that faces network issues; it can even be a failed rollout that is not fully in effect. In this case, a retry makes sense. By retrying, the request will be routed to the next server behind the load balancer.

We will try a request that has a bad status:

Java
 
@Test
void test5xxResponse() throws IOException {
    MockWebServer server = new MockWebServer();
    MockResponse tooManyRequests = new MockResponse()
            .setBody("Server Error")
            .setResponseCode(500);
    MockResponse successfulRequests = new MockResponse()
            .setBody("successful");
 
    server.enqueue(tooManyRequests);
    server.enqueue(tooManyRequests);
    server.enqueue(successfulRequests);
    server.start();
 
    WebClient webClient = WebClient.builder()
            .baseUrl("http://" + server.getHostName() + ":" + server.getPort())
            .build();
 
    Mono<String> result = webClient.get()
            .retrieve()
            .bodyToMono(String.class)
            .retry(2);
 
    StepVerifier.create(result)
            .expectNextMatches(s -> s.equals("successful"))
            .verifyComplete();
 
    server.shutdown();
}


Also, a response with the wrong format is possible to happen if an application goes haywire:

Java
 
@Data
@AllArgsConstructor
@NoArgsConstructor
private static class UsernameResponse {
    private String username;
}
 
@Test
void badFormat() throws IOException {
    MockWebServer server = new MockWebServer();
    MockResponse tooManyRequests = new MockResponse()
            .setBody("Plain text");
    MockResponse successfulRequests = new MockResponse()
            .setBody("{\"username\":\"test\"}")
            .setHeader("Content-Type","application/json");
 
    server.enqueue(tooManyRequests);
    server.enqueue(tooManyRequests);
    server.enqueue(successfulRequests);
    server.start();
 
    WebClient webClient = WebClient.builder()
            .baseUrl("http://" + server.getHostName() + ":" + server.getPort())
            .build();
 
    Mono<UsernameResponse> result = webClient.get()
            .retrieve()
            .bodyToMono(UsernameResponse.class)
            .retry(2);
 
    StepVerifier.create(result)
            .expectNextMatches(s -> s.getUsername().equals("test"))
            .verifyComplete();
 
    server.shutdown();
}


If we break it down, we created two responses in plain text format. Those responses would be rejected since they cannot be mapped to the UsernameResponse object. Thanks to the retries we managed to get a successful response.

Our last request would tackle the case of a timeout:

Java
 
@Test
void badTimeout() throws IOException {
    MockWebServer server = new MockWebServer();
    MockResponse dealayedResponse= new MockResponse()
            .setBody("Plain text")
            .setSocketPolicy(SocketPolicy.DISCONNECT_DURING_RESPONSE_BODY)
            .setBodyDelay(10000, TimeUnit.MILLISECONDS);
    MockResponse successfulRequests = new MockResponse()
            .setBody("successful");
 
    server.enqueue(dealayedResponse);
    server.enqueue(successfulRequests);
    server.start();
 
    WebClient webClient = WebClient.builder()
            .baseUrl("http://" + server.getHostName() + ":" + server.getPort())
            .build();
 
    Mono<String> result = webClient.get()
            .retrieve()
            .bodyToMono(String.class)
            .timeout(Duration.ofMillis(5_000))
            .retry(1);
 
    StepVerifier.create(result)
            .expectNextMatches(s -> s.equals("successful"))
            .verifyComplete();
 
    server.shutdown();
}


That’s it. Thanks to retries, our codebase was able to recover from failures and become more resilient. Also, we used MockWebServer, which can be very handy for simulating these scenarios.

Java (programming language) Requests Timeout (computing) Spring Framework

Published at DZone with permission of Emmanouil Gkatziouras. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Distributed Tracing System (Spring Cloud Sleuth + OpenZipkin)
  • How To Build Web Service Using Spring Boot 2.x
  • Why Queues Don’t Fix Scaling Problems
  • How to Identify the Underlying Causes of Connection Timeout Errors for MongoDB With Java

Partner Resources

×

Comments

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

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

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 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook