Making your RestTemplate fault tolerant with Resilience4J

HTTP communication is fragile and we have to make sure our applications are behaving properly in case of errors. A frequent approach to HTTP in the Spring world is to use RestTemplate. Let’s protect it with Resilience4J and its CircuitBreaker.

Integrating Resilience4J with Spring and RestTemplate seems to be an easy task but you can definitely run into a few difficulties so I’m going to show you exactly how to do it either using annotations or programmatically.

Since we’re going to implement Resilience4J circuit breaking into a Spring app using RestTemplate, it’s essential to understand what a CircuitBreaker is. I wrote about that in this article: Testing Spring Cloud Feign client resiliency using Resilience4J and even though the title suggests Feign, I explain circuit breaking at the beginning. Go there and check it out first.

The example app

Let’s create a really simple example app on start.spring.io. We’ll need the Web, Actuator and Lombok dependencies.

We’ll create an API endpoint: /foo.

@RestController
@RequiredArgsConstructor
public class ApiController {

    @GetMapping("/foo")
    public String foo() {
        ...
    }
}

Next up, we’ll create an adapter for the external service API using RestTemplate.

@Component
@RequiredArgsConstructor
public class ExternalApi {
    private final RestTemplate restTemplate;

    public String callExternalApiFoo() {
        return restTemplate.getForObject("/external-foo", String.class);
    }
}

The external API is going to be found on the /external-foo path. Now let’s configure the RestTemplate.

@Configuration
public class ExternalApiConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplateBuilder().rootUri("http://localhost:9090").build();
    }
}

That’s all. That’s the foundation for the example app.

For testing purposes I’ll not create a completely new service and implementing the API but I’ll create a WireMock server and a test case to simulate the scenario. First lets add the WireMock dependency:

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

And then the test:

@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 will start up the Spring Boot app, then it’ll start the WireMock server on port 9090 (the port we set for the RestTemplate) and then we have the test case.

And I actually stumbled upon an issue when writing the test. I was trying to use MockMvc for invoking the API we wrote above, /foo. Since the WireMock server is configured to return an HTTP 500 in this line:

EXTERNAL_SERVICE.stubFor(get("/external-foo").willReturn(serverError()));

The underlying RestTemplate will will throw an exception. And when using MockMvc, the exception itself was popping up in the test instead of getting an HTTP 500 for unhandled exceptions in a Spring Boot webapp.

Turns out, this is an expected behavior and for full-fledged web testing, you should use the TestRestTemplate for invoking APIs within the tests.

I was like, okay, let’s do it. But then I realized how do I get a configured TestRestTemplate instance into the test so I can autowire it?

Another magic that I realized, you should configure a webEnvironment on the @SpringBootTest annotation and you get a pre-configured TestRestTemplate. Otherwise Spring will not even start everything necessary as in a regular web app.

So this line is absolutely mandatory:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

If you switch it up to this:

@SpringBootTest({"server.port:0"})

It’s not going to work. Good learning for me, hopefully you’ll not run into this issue anymore either.

Anyway, getting back to the test.

    @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 case will validate that the first 5 request for the /foo API will respond with an HTTP 500 and the next 5 will result in HTTP 503 Service Unavailable. Pretty much the first 5 requests will indicate that the external service is crashing and after the first 5 requests, the circuit breaker will open and stop requests going through.

Now if you start the tests, they’ll fail because the second half of API calls will not result in HTTP 503 so let’s implement the CircuitBreaker with Resilience4J.

Annotation based Resilience4J CircuitBreaker

First things first, we’ll need the necessary dependencies for Resilience4J and Spring Boot. The following 2 goes into the build.gradle.

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

Then, let’s mark the external API with the @CircuitBreaker annotation:

@Component
@RequiredArgsConstructor
public class ExternalApi {
    private final RestTemplate restTemplate;

    @CircuitBreaker(name = "externalServiceFoo")
    public String callExternalApiFoo() {
        return restTemplate.getForObject("/external-foo", String.class);
    }
}

The name attribute we provided in the annotation refers to which CircuitBreaker configuration it should pick up.

Let’s go back to our configuration class and add the externalServiceFoo CircuitBreaker configuration:

@Configuration
public class ExternalApiConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplateBuilder().rootUri("http://localhost:9090").build();
    }

    @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, the evaluation will happen after the first 5 calls are done and we’ll wait 5 seconds in OPEN state and the failure threshold is set to 50% meaning that out of 10 requests, if 5 fails, the CircuitBreaker will open.

Since if the CircuitBreaker is open, Resilience4J will throw a CallNotPermittedException which in the default case, Spring will translate into an HTTP 500. The test we wrote above expects that after the failure threshold is hit, the server will respond with an HTTP 503 – Service Unavailable, so let’s make that happen.

I’ll use Spring’s exception handling:

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

This exception handler translates the CallNotPermittedException into an HTTP 503.

Awesome, now we should run the test case. Let’s do it.

Failing, but why?

expected: 503 SERVICE_UNAVAILABLE
 but was: 500 INTERNAL_SERVER_ERROR
org.opentest4j.AssertionFailedError: 
expected: 503 SERVICE_UNAVAILABLE
 but was: 500 INTERNAL_SERVER_ERROR

So it seems the service keeps crashing instead of responding with an HTTP 503 which indicates that the CircuitBreaker doesn’t work.

Turns out, it’s mandatory to define at least one property for any CircuitBreaker in the application.properties so Resilience4J picks it up.

Go to the application properties and put this line there:

resilience4j.circuitbreaker.instances.externalServiceFoo.slidingWindowType=COUNT_BASED

Now restart the test and it should be green.

This is unfortunately a limitation in the current Resilience4J – Spring Boot integration. It would be much better if the configurations could be loaded dynamically. As far as I can tell, right now the reason for this limitation is that the CircuitBreakerConfigCustomizer gets picked up on startup but Resilience4J doesn’t even know there’s a CircuitBreaker used with the externalServiceFooname so it simply ignores it. When the configuration property is set, it’ll create a default CircuitBreaker with the properties defined and the customizer will override the defaults.

Anyway, I think this’ll be fixed in the future, at least I hope so. But you can see how easy it is to integrate Resilience4J and make your RestTemplate calls more resilient to failures.

Programmatic Resilience4J CircuitBreaker

Some of us don’t like the magic annotations, that’s a fact. In this particular case with the additional property definition to make everything work, I don’t necessarily like it either.

Fortunately we can configure and use the CircuitBreaker programmatically as well.

The main store for the Resilience4J CircuitBreakers and all other related objects is a registry. For CircuitBreaker it’s called CircuitBreakerRegistry. Defining one as a bean will give us all the control we need.

We can remove the property we added from the application.properties file. Then let’s change the configuration class a little bit:

@Configuration
public class ExternalApiConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplateBuilder().rootUri("http://localhost:9090").build();
    }

    @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)
        );
    }
}

Very similar to the previous approach.

And the usage is the following:

@Component
@RequiredArgsConstructor
public class ExternalApi {
    private final CircuitBreakerRegistry circuitBreakerRegistry;
    private final RestTemplate restTemplate;

    public String callExternalApiFoo() {
        CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("externalServiceFoo", "externalServiceFoo");
        return circuitBreaker.executeSupplier(() -> restTemplate.getForObject("/external-foo", String.class));
    }
}

Getting the registry reference in the constructor and retrieving the proper CircuitBreaker instance that we pre-configured and using the executeSupplier method to decorate the original RestTemplate call.

The test can stay the same, no change is necessary. Just start it up and it should be green.

RestTemplate with Resilience4J is possible

As you saw, it’s awfully easy to integrate Resilience4J with RestTemplate calls. You definitely shouldn’t skip implementing some sort of resiliency pattern into your external service calls because they can and they will fail.

If this was useful, let me know in the comments below. You can reach me on Twitter or Facebook for any discussion and make sure to follow me for more stuff like this.

The annotation based configuration can be found on GitHub here.

Leave a Reply

Your email address will not be published.