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 externalServiceFoo
name 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.
Hi ,
I have implemented same way with Spring MVC , but its not working. Plz do help. Thanks..!