Learnitweb

Implementing Circuit Breaker Pattern in Gateway

Introduction

In this tutorial, we’ll implement circuit breaker pattern in gateway using resilience4j.

Dependency

Add the following dependency to the pom.xml:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId> </dependency>
<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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@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();
}
@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(); }
	@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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
resilience4j.circuitbreaker:
configs:
default:
slidingWindowSize: 10
permittedNumberOfCallsInHalfOpenState: 2
failureRateThreshold: 50
waitDurationInOpenState: 10000
resilience4j.circuitbreaker: configs: default: slidingWindowSize: 10 permittedNumberOfCallsInHalfOpenState: 2 failureRateThreshold: 50 waitDurationInOpenState: 10000
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
  • 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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
"circuitBreakers": {
}
}
{ "circuitBreakers": { } }
{
  "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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
"timestamp": "2025-02-08T23:58:43.129+00:00",
"path": "/eshop/productservice/api/inventory/car",
"status": 504,
"error": "Gateway Timeout",
"requestId": "4739a85e-5"
}
{ "timestamp": "2025-02-08T23:58:43.129+00:00", "path": "/eshop/productservice/api/inventory/car", "status": 504, "error": "Gateway Timeout", "requestId": "4739a85e-5" }
{
    "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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
"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"
}
}
}
{ "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" } } }
{
  "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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
"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"
}
}
}
{ "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" } } }
{
  "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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
"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"
}
}
}
{ "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" } } }
{
  "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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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");
}
}
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"); } }
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
@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();
}
@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(); }
	@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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
An error occurred. Please try after some time or contact support team
An error occurred. Please try after some time or contact support team
An error occurred. Please try after some time or contact support team