Making your Spring WebClient fault tolerant with Resilience4J

This time we’ll dive into how to integrate a Resilience4J CircuitBreaker with a Spring WebClient. Last time I showed the same thing for Spring’s RestTemplate but since WebClient is getting more and more hype, I thought let’s cover it as well.

Similarly to the previous post, I’ll show you 2 ways to integrate Resilience4J with a WebClient. First with annotations and then programmatically. Both are going to be fairly easy.

Again, if you’re not familiar with circuit breaking and what a CircuitBreaker is, check out one of the articles I wrote on this topic: Testing Spring Cloud Feign client resiliency using Resilience4J and even though the title suggests Feign, I explain circuit breaking at the very beginning.

The example app

The app is gonna be simple and quite similar to what I used in the previous article. I generated a project on start.spring.io with Web Reactive, Actuator and Lombok dependencies.

A simple API endpoint for the app:

@RestController
@RequiredArgsConstructor
public class ApiController {
    private final ExternalApi externalApi;

    @GetMapping("/foo")
    public Mono<String> foo() {
        return externalApi.callExternalApiFoo();
    }
}

What’s important here is the fact that the return type from the method is not a simple String but a Mono<String> since the app is using Web Reactive.

Let’s create the ExternalApi class.

@Component
@RequiredArgsConstructor
public class ExternalApi {
    private final WebClient webClient;

    public Mono<String> callExternalApiFoo() {
        return webClient.get().uri("/external-foo").retrieve().bodyToMono(String.class);
    }
}

The WebClient is calling the API on the /external-foo path and resolves the response body into a String. The returning type will be again a Mono<String> since we’re in the reactive world.

Now the WebClient configuration.

@Configuration
public class ExternalApiConfig {
    @Bean
    public WebClient webClient() {
        return WebClient.create("http://localhost:9090");
    }
}

And that’s all for now. The WebClient will invoke the http://localhost:9090/external-foo API.

Instead of creating a brand new Spring app to implement the /external-foo API, I’ll use WireMock to set up a mock server and I’ll write a test-case to verify the scenario we want.

Let’s add the WireMock dependency first into the build.gradle.

testImplementation "com.github.tomakehurst:wiremock-jre8:2.31.0"

And then the test case:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class ApiControllerTest {
    @RegisterExtension
    static WireMockExtension EXTERNAL_SERVICE = WireMockExtension.newInstance()
            .options(WireMockConfiguration.wireMockConfig().port(9090))
            .build();

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void testFoo() throws Exception {
        EXTERNAL_SERVICE.stubFor(get("/external-foo").willReturn(serverError()));

        for (int i = 0; i < 5; i++) {
            ResponseEntity<String> response = restTemplate.getForEntity("/foo", String.class);
            assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
        }
        for (int i = 0; i < 5; i++) {
            ResponseEntity<String> response = restTemplate.getForEntity("/foo", String.class);
            assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
        }
    }
}

The test is going to start up a WireMock server on port 9090. It’ll also start up a Spring Boot web server and then the WireMock server will respond with HTTP 500s on the /external-foo API endpoint.

Then the first 5 calls for the /foo API that our Spring app provides should fail with HTTP 500 since there will be uncaught exceptions in the app by the WebClient and by default those get translated into HTTP 500s as well.

After the 5 API calls, the next 5 should fail with HTTP 503 – Service Unavailable, indicating that the Resilience4J CircuitBreaker has opened.

Note: there were a couple tricks you should look out for so I recommend reading the other article because I explain them there: Making your RestTemplate fault tolerant with Resilience4J.

If you run the test case, it’ll obviously fail because we don’t have any CircuitBreaker set up, yet.

Annotation based Resilience4J CircuitBreaker

For annotation driven CircuitBreakers we’ll need a couple dependencies:

	implementation 'org.springframework.boot:spring-boot-starter-aop'
	implementation 'io.github.resilience4j:resilience4j-spring-boot2:1.7.1'
	implementation "io.github.resilience4j:resilience4j-reactor:1.7.1"

The last dependency io.github.resilience4j:resilience4j-reactor:1.7.1 is obviously only needed if you’re running a reactive Spring app.

Now, let’s use the annotation Resilience4J provides:

@Component
@RequiredArgsConstructor
public class ExternalApi {
    private final WebClient webClient;

    @CircuitBreaker(name = "externalServiceFoo")
    public Mono<String> callExternalApiFoo() {
        return webClient.get().uri("/external-foo").retrieve().bodyToMono(String.class);
    }
}

That’s it. The CircuitBreaker annotation on the method. Due to the fact we have the resilience4j-reactor dependency, it’ll recognize the Mono return type and will automatically compose circuit breaking into the execution flow. Awesome right?

The externalServiceFoo in the annotation is the name of our CircuitBreaker that we’ll configure in a second.

Let’s go back to our config class and add a CircuitBreakerCustomizer.

@Configuration
public class ExternalApiConfig {
    @Bean
    public WebClient webClient() {
        return WebClient.create("http://localhost:9090");
    }

    @Bean
    public CircuitBreakerConfigCustomizer externalServiceFooCircuitBreakerConfig() {
        return CircuitBreakerConfigCustomizer
                .of("externalServiceFoo",
                        builder -> builder.slidingWindowSize(10)
                                .slidingWindowType(COUNT_BASED)
                                .waitDurationInOpenState(Duration.ofSeconds(5))
                                .minimumNumberOfCalls(5)
                                .failureRateThreshold(50.0f));
    }
}

This will configure the CircuitBreaker to have a COUNT_BASED sliding window with a size of 10. It won’t evaluate the CircuitBreaker for the first 5 calls (minimumNumberOfCalls) and will trigger at 50% failure rate; waiting 5 seconds in OPEN state.

Since the test is expecting an HTTP 503 – Service Unavailable response after the CircuitBreaker is open, we have to implement that exception handling first.

We’re creating a new exception handler:

@ControllerAdvice
public class ApiExceptionHandler {
    @ExceptionHandler({CallNotPermittedException.class})
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public void handle() {
    }
}

This’ll do the job.

Now the test case will still fail if you start it. Why?

It’s because with the CircuitBreakerCustomizer we can only override existing configuration. In our particular case we’re overriding the externalServiceFoo configuration which apparently doesn’t exist yet – in the context of Resilience4J.

How to make Resilience4J aware of the CircuitBreaker configuration?

Easy, just go to the application.properties and add this line:

resilience4j.circuitbreaker.instances.externalServiceFoo.slidingWindowType=COUNT_BASED

This will create the configuration object with default settings in Resilience4J’s CircuitBreaker registry and then the values we provided will override the defaults.

You can restart the test now and it should be green. Great job.

Programmatic Resilience4J CircuitBreaker composition

If you don’t like the magical annotations and the concept of aspect oriented programming, you can use a Resilience4J CircuitBreaker programmatically as well.

First of all, we have to create the CircuitBreaker configuration. It’s stored in a CircuitBreakerRegistry class that’s provided by Resilience4J.

@Configuration
public class ExternalApiConfig {
    @Bean
    public WebClient webClient() {
        return WebClient.create("http://localhost:9090");
    }

    @Bean
    public CircuitBreakerRegistry circuitBreakerRegistry() {
        CircuitBreakerConfig externalServiceFooConfig = CircuitBreakerConfig.custom()
                .slidingWindowSize(10)
                .slidingWindowType(COUNT_BASED)
                .waitDurationInOpenState(Duration.ofSeconds(5))
                .minimumNumberOfCalls(5)
                .failureRateThreshold(50.0f)
                .build();
        return CircuitBreakerRegistry.of(
                Map.of("externalServiceFoo", externalServiceFooConfig)
        );
    }
}

Same CircuitBreaker settings, just another way of configuring it. And we can remove the line from the application.properties, we won’t need that anymore.

How to use it with a WebClient? Easy.

@Component
@RequiredArgsConstructor
public class ExternalApi {
    private final CircuitBreakerRegistry circuitBreakerRegistry;
    private final WebClient webClient;

    public Mono<String> callExternalApiFoo() {
        CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("externalServiceFoo", "externalServiceFoo");
        return webClient.get()
               .uri("/external-foo")
               .retrieve()
               .bodyToMono(String.class)
               .transformDeferred(CircuitBreakerOperator.of(circuitBreaker));
    }
}

We autowire the CircuitBreakerRegistry to get access to the pre-configured CircuitBreaker. In the method, we retrieve the CircuitBreaker instance and then use the CircuitBreakerOperator from the resilience4j-reactor module to compose the API call with the CircuitBreaker instance.

Of course we don’t need to change anything in the test since we only modified the internal implementation how we achieve circuit breaking.

If you run the test case, it should pass.

WebClient with Resilience4J is easy

As you see, it’s quite easy to integrate Resilience4J with a Spring WebClient for resiliency purposes. Using a CircuitBreaker is just the first step on the road; there are much more to Resilience4J that you can use similarly to a CircuitBreaker.

If you liked it, let me know in the comments below. You can reach me on Twitter and Facebook if you have any questions or have something in mind.

You can find the annotation based configuration here on GitHub.

4 Replies to “Making your Spring WebClient fault tolerant with Resilience4J”

  1. Rajat says:
    1. Arnold Galovics says:

Leave a Reply

Your email address will not be published.