Introduction
In this tutorial, we’ll implement circuit breaker pattern in gateway using resilience4j.
Dependency
Add the following dependency to the pom.xml
:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId> </dependency>
spring-cloud-starter-circuitbreaker-reactor-resilience4j
is a Spring Boot starter that integrates Resilience4j with Spring Cloud Circuit Breaker in reactive applications using Project Reactor (WebFlux).
Adding Circuit Breaker
In the earlier tutorials we added filters to rewrite path.
@Bean public RouteLocator eshopAppRouterConfig(RouteLocatorBuilder routeLocatorBuilder) { return routeLocatorBuilder.routes() .route(p -> p.path("/eshop/productservice/**") .filters(f -> f.rewritePath("/eshop/productservice/(?<segment>.*)", "/${segment}") .circuitBreaker(config -> config.setName("productCircuitBreaker"))) .uri("lb://PRODUCTSERVICE")) .build(); }
In the filters, add the changes for circuitbreaker.
The change wraps the route in a circuit breaker named "productCircuitBreaker"
.
Add the following to the application.yml
:
resilience4j.circuitbreaker: configs: default: slidingWindowSize: 10 permittedNumberOfCallsInHalfOpenState: 2 failureRateThreshold: 50 waitDurationInOpenState: 10000
The provided YAML configuration defines the default behavior of the Resilience4j Circuit Breaker. This configuration helps in handling failures effectively in microservices.
slidingWindowSize: 10
- Determines how many calls should be monitored to decide if the circuit breaker should open.
- A window of 10 requests is considered when calculating the failure rate.
- Example: If 5 out of 10 requests fail, the circuit breaker opens (based on
failureRateThreshold
).
permittedNumberOfCallsInHalfOpenState: 2
- When the circuit breaker transitions from OPEN to HALF-OPEN, it allows only 2 test requests.
- If both succeed, the circuit closes (returns to normal operation).
- If either one fails, the circuit stays open.
failureRateThreshold: 50
- Defines the percentage of failed requests (within
slidingWindowSize
) that triggers the circuit breaker. - Here, if 50% or more requests fail, the circuit breaker opens.
- Example:
- Total requests (sliding window) = 10
- Failed requests = 5 (50%) → Circuit breaker opens
- Failed requests = 4 (40%) → Circuit remains closed
- Defines the percentage of failed requests (within
waitDurationInOpenState: 10000
(in milliseconds)- Defines how long the circuit breaker stays open before switching to HALF-OPEN.
- Here, it waits 10 seconds (10,000 ms) before testing the system with a few requests.
- If the system recovers, the circuit closes; otherwise, it remains open.
Circuit Breaker Flow Based on This Configuration
- Normal (Closed) State
- All requests pass through.
- Failure rate is monitored over 10 requests.
- Open State (Triggered if 50% of last 10 calls fail)
- Requests fail immediately.
- Waits 10 seconds before testing recovery.
- Half-Open State (After 10s Wait Time)
- 2 requests are allowed as a test.
- If both succeed → Circuit closes (normal operation).
- If any fail → Circuit stays open.
Testing our changes
To start the changes start Eureka server, gateway server, product service and inventory service.
The http://localhost:8072/actuator/circuitbreakers
endpoint will provide runtime insights into all configured Resilience4j circuit breakers in your application. Initially the output looks like the following:
{ "circuitBreakers": { } }
To demonstrate the circuit breaker, add a breakpoint in /inventory/{name}
controller.
If you try to hit the endpoint localhost:8072/eshop/productservice/api/inventory/car
, since the breakpoint is not released, you should get the following as output:
{ "timestamp": "2025-02-08T23:58:43.129+00:00", "path": "/eshop/productservice/api/inventory/car", "status": 504, "error": "Gateway Timeout", "requestId": "4739a85e-5" }
Now check the endpoint http://localhost:8072/actuator/circuitbreakers
. The output will be like this:
{ "circuitBreakers": { "productCircuitBreaker": { "failureRate": "-1.0%", "slowCallRate": "-1.0%", "failureRateThreshold": "50.0%", "slowCallRateThreshold": "100.0%", "bufferedCalls": 1, "failedCalls": 1, "slowCalls": 0, "slowFailedCalls": 0, "notPermittedCalls": 0, "state": "CLOSED" } } }
If you keep trying with the current state of the application, the circuit breaker will open.
{ "circuitBreakers": { "productCircuitBreaker": { "failureRate": "100.0%", "slowCallRate": "0.0%", "failureRateThreshold": "50.0%", "slowCallRateThreshold": "100.0%", "bufferedCalls": 10, "failedCalls": 10, "slowCalls": 0, "slowFailedCalls": 0, "notPermittedCalls": 0, "state": "OPEN" } } }
Now, remove the breakpoint and try to get successful response. This will result in circuit breaker to be HALF_OPEN.
{ "circuitBreakers": { "productCircuitBreaker": { "failureRate": "-1.0%", "slowCallRate": "-1.0%", "failureRateThreshold": "50.0%", "slowCallRateThreshold": "100.0%", "bufferedCalls": 1, "failedCalls": 0, "slowCalls": 0, "slowFailedCalls": 0, "notPermittedCalls": 0, "state": "HALF_OPEN" } } }
If you try one more time with success, the state of the circuit breaker will be CLOSED.
Fallback Mechanism
Currently, we have implemented the Circuit Breaker pattern within the Gateway server, but it lacks a fallback mechanism.
Without a proper fallback, the system simply throws runtime exceptions, such as “Service Unavailable” or “Gateway Timeout,” when a service fails. However, in real-world business applications, exposing raw exceptions to client applications or UI components is not a recommended practice.
To address this, we need a fallback mechanism that provides meaningful responses to clients. Instead of runtime exceptions, the fallback logic should return informative messages that help client applications handle failures gracefully. Implementing such a mechanism ensures a better user experience and improves system resilience.
Create a fallback endpoint to handle the case when there is an issue:
package com.learnitweb.gatewayserver.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; @RestController public class FallbackController { @RequestMapping("/contactSupport") public Mono<String> contactSupport() { return Mono.just("An error occurred. Please try after some time or contact support team"); } }
Now use setFallbackUri
method to set the fallback uri:
@Bean public RouteLocator eshopAppRouterConfig(RouteLocatorBuilder routeLocatorBuilder) { return routeLocatorBuilder.routes().route(p -> p.path("/eshop/productservice/**") .filters(f -> f.rewritePath("/eshop/productservice/(?<segment>.*)", "/${segment}").circuitBreaker( config -> config.setName("productCircuitBreaker").setFallbackUri("forward:/contactSupport"))) .uri("lb://PRODUCTSERVICE")).build(); }
Now if we there is something wrong, fallback uri will be called and you will get the following response.
An error occurred. Please try after some time or contact support team