Circuit Breaker in Spring Boot (Spring Cloud Circuit Breaker + Resilience4j)

  • 4.5/5
  • 4228
  • Oct 30, 2024

What is circuit breaker pattern in microservices?

The circuit breaker pattern in microservices is a design pattern that helps prevent cascading failures in distributed systems by stopping or breaking the connection to a failing service or component after a threshold of failures is reached.

How It Works?

The circuit breaker monitors the number of failures or error rates over time and has three main states:

Closed: Requests are routed normally. The circuit breaker counts the number of recent failures. If these failures exceed a certain threshold within a defined window, the breaker switches to the Open state.

Open: Requests to the failing service are stopped immediately. Instead of making a call that’s likely to fail, the circuit breaker immediately returns an error or executes a fallback (alternative response). After a cooldown period, it moves to the Half-Open state.

Half-Open: A limited number of requests are allowed through to test if the service has recovered. If requests succeed, the circuit breaker resets to Closed; otherwise, it returns to Open.

What is Spring Cloud Circuit Breaker?

Spring Cloud Circuit Breaker is a library that provides an easy-to-use abstraction layer for implementing circuit breakers in Spring Boot applications, offering seamless integration with popular resilience libraries like Resilience4j and Spring Retry.

The following starters are available with the Spring Cloud BOM:

1) Resilience4j: org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j

2) Reactive Resilience4j: org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j

3) Spring Retry: org.springframework.cloud:spring-cloud-starter-circuitbreaker-spring-retry

In this article, we implement a circuit breaker with Resilience4j using org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j.

How to Implement Spring Cloud Circuit Breaker with Resilience4j?

Usecase

Imagine a microservices-based e-commerce application where an order-service relies on an inventory-service. If the "inventory-service" becomes slow or fails completely, the circuit breaker in "order-service" will "open" after several failed requests, stopping further requests to "inventory-service".

The "order-service" can then provide a fallback response (like 'inventory data is currently unavailable') to prevent impacting the end-user experience.

Implementation

inventory-service

This is a simple Spring Boot web application with only one endpoint, so there is no need to explain it here.

The source code for this application can be found here: Github.

order-service

Now, let's implement Spring Cloud Circuit Breaker using Resilience4j in the order-service, which calls inventory-service with a count-based circuit breaker.

1) Use Spring Initializr (Web Interface)

1.1) Open Spring Initializr in your browser.

1.2) Choose either Gradle - Groovy or Gradle - Kotlin as the build tool, based on your preferred language.

1.3) Select dependencies for your project, including Spring Web, Spring Boot Actuator, Resilience4J, and any other required dependencies.

1.4) Click Generate to download the project as a ZIP file.

1.5) Unzip the downloaded file, and open it in your preferred IDE, such as IntelliJ IDEA or VS Code.

2) Add Dependencies

Add the necessary dependencies to your build.gradle file. The final configuration should look something like this:

plugins {
id 'java'
id 'org.springframework.boot' version '3.3.4'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'com.cb'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
ext {
set('springCloudVersion', "2023.0.3")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j'
implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.6.0'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-aop'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}
view raw build.gradle hosted with ❤ by GitHub

The spring-boot-starter-web dependency is used for building REST APIs. The spring-boot-starter-actuator dependency enables monitoring and management endpoints. The optional Lombok dependencies help reduce boilerplate code. The optional spring-boot-devtools dependency provides hot reloading to enhance development productivity.

3) Configure Properties

In your application.yml, add the following configurations for the inventory-service URL, Resilience4j circuit breaker, Swagger UI, and Actuator to expose the circuit breaker health endpoint.

spring:
application:
name: order-service
# Configuration for the inventory service
inventory:
service:
url: http://localhost:8081/inventory/
# Configuration for resilience4j circuit breaker
resilience4j:
circuitbreaker:
instances:
inventory-circuit-breaker:
registerHealthIndicator: true
slidingWindowType: COUNT_BASED
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 5000 # 5 seconds
permittedNumberOfCallsInHalfOpenState: 3
automaticTransitionFromOpenToHalfOpenEnabled: true
slowCallRateThreshold: 50
slowCallDurationThreshold: 2000 # 2 seconds
# Configuration for Swagger UI
springdoc:
swagger-ui:
path: /swagger-ui.html
# Configuration for Spring Boot Actuator
management:
endpoints:
web:
exposure:
include: "*"
health:
show-details: always
health.circuitbreakers.enabled: true
# logging configuration
logging:
level:
io:
github:
resilience4j:
circuitbreaker: DEBUG
view raw application.yml hosted with ❤ by GitHub
4) Order Model

The Order model represents an order in the order-service. It includes basic fields like orderId, itemId, itemName, and quantity.

package com.cb.model;
/**
* Record representing an Order.
*
* @param orderId the ID of the order
* @param itemId the ID of the item in the order
* @param itemName the name of the item in the order
* @param quantity the quantity of the item in the order
*/
public record Order(String orderId, String itemId, String itemName, int quantity) {
}
view raw Order.java hosted with ❤ by GitHub
5) InventoryResponse Model

The InventoryResponse model represents the response from the inventory-service, containing fields for product availability status and possibly additional details.

package com.cb.client.response;
/**
* A record representing the response from the inventory service.
*
* @param id the ID of the inventory item
* @param name the name of the inventory item
* @param quantity the quantity of the inventory item
*/
public record InventoryResponse(String id, String name, int quantity) {
}
6) Create InventoryServiceClient

Define a RestClient that will make a REST call to the inventory-service. Use the @CircuitBreaker annotation provided by Spring Cloud Circuit Breaker to apply the circuit breaker to the method.

package com.cb.client;
import com.cb.client.response.InventoryResponse;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
/**
* Client for interacting with the inventory service.
*/
@Component
public class InventoryClient {
private static final Logger logger = LoggerFactory.getLogger(InventoryClient.class);
private final RestTemplate restTemplate;
private final String inventoryServiceUrl;
/**
* Constructs an InventoryClient with the specified RestTemplate and inventory service URL.
*
* @param restTemplate the RestTemplate to use for HTTP requests
* @param inventoryServiceUrl the base URL of the inventory service
*/
public InventoryClient(RestTemplate restTemplate, @Value("${inventory.service.url}") String inventoryServiceUrl) {
this.restTemplate = restTemplate;
this.inventoryServiceUrl = inventoryServiceUrl;
}
/**
* Fetches an inventory item by its ID, with circuit breaker support.
*
* @param itemId the ID of the inventory item to fetch
* @return the fetched InventoryResponse, or null if the request fails
*/
@CircuitBreaker(name = "inventory-circuit-breaker", fallbackMethod = "fallbackGetInventoryItem")
public InventoryResponse getInventoryItem(String itemId) {
String url = inventoryServiceUrl + itemId;
logger.info("Fetching inventory item from URL: {}", url);
var response = restTemplate.getForObject(url, InventoryResponse.class);
logger.info("Received inventory item: {}", response);
return response;
}
/**
* Fallback method for getInventoryItem, called when the circuit breaker is open or an error occurs.
*
* @param itemId the ID of the inventory item that was attempted to be fetched
* @param t the throwable that caused the fallback
* @return null, or a default InventoryResponse
*/
public InventoryResponse fallbackGetInventoryItem(String itemId, Throwable t) {
logger.error("Error fetching inventory for itemId: {}, so falling back to default response", itemId, t);
// or you can return a default InventoryResponse
return new InventoryResponse("default", "Default Item", 0);
}
}

The @CircuitBreaker annotation applies the circuit breaker to the getInventoryItem method.

The fallback method is called when the circuit breaker is open or the call fails.

7) Register RestTemplate Bean

Ensure a RestTemplate bean is available for dependency injection in your InventoryClient:

package com.cb.client.conf;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* Configuration class for client-related beans.
*/
@Configuration
public class ClientConfig {
/**
* Creates a {@link RestTemplate} bean.
*
* @return a new instance of {@link RestTemplate}
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
8) Use InventoryClient in the OrderController

Now, inject InventoryClient in OrderController and use it to call the inventory-service.

package com.cb.controller;
import com.cb.client.InventoryClient;
import com.cb.model.Order;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* REST controller for handling order-related requests.
*/
@RestController
public class OrderController {
private final InventoryClient inventoryClient;
public OrderController(InventoryClient inventoryClient) {
this.inventoryClient = inventoryClient;
}
/**
* Retrieves an order by its ID.
*
* @param orderId the ID of the order to retrieve
* @return the Order object containing order details
*/
@GetMapping("/order/{orderId}")
public Order getOrder(@PathVariable String orderId) {
// Dummy itemId for demonstration purposes
var itemId = "dummyItemId";
var inventoryResponse = inventoryClient.getInventoryItem(itemId);
// Create a dummy Order Response using the inventory data
return new Order(orderId, itemId, inventoryResponse.name(), inventoryResponse.quantity());
}
}
9) Run the Application

Start your Spring Boot application. After starting, the Swagger UI should be accessible at: http://localhost:8080/swagger-ui.html

10) Verify Circuit Breaker Behavior

Using Spring Boot Actuator, you can view circuit breaker metrics at the /actuator/circuitbreakers endpoint.

Trigger a few failed responses by having the inventory-service throw a RuntimeException or by shutting down the service. Once the configured failure threshold is reached, the circuit breaker will open, causing subsequent calls to return the fallback response.

After enough time has passed (e.g., 60 seconds, as per your configuration), the circuit breaker transitions to the half-open state.

As we have set the logging level for "io.github.resilience4j.circuitbreaker" to DEBUG, this will allow us to see the following informative logs in the application logs.

2024-10-28T01:16:15.470+05:30 DEBUG 6115 --- [order-service] [io-8080-exec-10] i.g.r.c.i.CircuitBreakerStateMachine     : CircuitBreaker 'inventory-circuit-breaker' recorded an exception as failure:
.
.
.
.
2024-10-28T01:16:15.923+05:30 DEBUG 6115 --- [order-service] [nio-8080-exec-1] i.g.r.c.i.CircuitBreakerStateMachine     : Event ERROR published: 2024-10-28T01:16:15.923560+05:30[Asia/Kolkata]: CircuitBreaker 'inventory-circuit-breaker' recorded an error: 'org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 : "{"timestamp":"2024-10-27T19:46:15.920+00:00","status":500,"error":"Internal Server Error","path":"/inventory/dummyItemId"}"'. Elapsed time: 7 ms
2024-10-28T01:16:15.924+05:30 DEBUG 6115 --- [order-service] [nio-8080-exec-1] i.g.r.c.i.CircuitBreakerStateMachine     : Event FAILURE_RATE_EXCEEDED published: 2024-10-28T01:16:15.924930+05:30[Asia/Kolkata]: CircuitBreaker 'inventory-circuit-breaker' exceeded failure rate threshold. Current failure rate: 100.0
2024-10-28T01:16:15.930+05:30 DEBUG 6115 --- [order-service] [nio-8080-exec-1] i.g.r.c.i.CircuitBreakerStateMachine     : Event STATE_TRANSITION published: 2024-10-28T01:16:15.930455+05:30[Asia/Kolkata]: CircuitBreaker 'inventory-circuit-breaker' changed state from CLOSED to OPEN
.
.
.
2024-10-28T01:16:20.939+05:30 DEBUG 6115 --- [order-service] [ransitionThread] i.g.r.c.i.CircuitBreakerStateMachine     : Event STATE_TRANSITION published: 2024-10-28T01:16:20.938948+05:30[Asia/Kolkata]: CircuitBreaker 'inventory-circuit-breaker' changed state from OPEN to HALF_OPEN

Unit Testing Circuit Breaker

This test class, InventoryClientTest, verifies the behavior of the InventoryClient class in different circuit breaker states:

package com.cb.client;
import com.cb.Application;
import com.cb.client.response.InventoryResponse;
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.web.client.RestTemplate;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
/**
* Test class for InventoryClient.
*/
@SpringBootTest(classes = Application.class)
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class})
@Execution(ExecutionMode.SAME_THREAD)
class InventoryClientTest {
@MockBean
private RestTemplate restTemplate;
@Value("${inventory.service.url}")
private String inventoryServiceUrl;
@MockBean
private CircuitBreaker circuitBreaker;
@Autowired
private InventoryClient inventoryClient;
@Autowired
private CircuitBreakerRegistry circuitBreakerRegistry;
/**
* Sets up the test environment before each test.
*/
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
/**
* Tests that getInventoryItem returns the item when the circuit breaker is closed.
*/
@Test
void getInventoryItem_ReturnsItem_WhenCircuitBreakerClosed() {
circuitBreakerRegistry.circuitBreaker("inventory-circuit-breaker")
.transitionToClosedState();
when(restTemplate.getForObject("http://localhost:8081/inventory/1", InventoryResponse.class))
.thenReturn(new InventoryResponse("1", "Item 1", 10));
InventoryResponse result = inventoryClient.getInventoryItem("1");
assertEquals("1", result.id(), "Item ID should match");
verify(restTemplate, times(1)).getForObject(anyString(), eq(InventoryResponse.class));
}
/**
* Tests that getInventoryItem returns the default item when the circuit breaker is open.
*/
@Test
void getInventoryItem_ReturnsDefault_WhenCircuitBreakerOpen() {
circuitBreakerRegistry.circuitBreaker("inventory-circuit-breaker")
.transitionToOpenState();
doThrow(new RuntimeException()).when(restTemplate).getForObject("http://localhost:8081/inventory/1", InventoryResponse.class);
InventoryResponse result = null;
try {
result = inventoryClient.getInventoryItem("1");
} catch (RuntimeException e) {
assertEquals(CallNotPermittedException.class, e.getClass(), "Circuit breaker should prevent the request");
verifyNoInteractions(restTemplate, "Request should not be made when circuit breaker is open");
} finally {
assertNotNull(result, "Result should not be null");
assertEquals("default", result.id(), "Default item should be returned");
}
}
/**
* Tests that getInventoryItem returns the item when the circuit breaker is half-open.
*/
@Test
void getInventoryItem_ReturnsItem_WhenCircuitBreakerHalfOpen() {
circuitBreakerRegistry.circuitBreaker("inventory-circuit-breaker")
.transitionToOpenState();
circuitBreakerRegistry.circuitBreaker("inventory-circuit-breaker")
.transitionToHalfOpenState();
doThrow(new RuntimeException()).when(restTemplate).getForObject("http://localhost:8081/inventory/1", InventoryResponse.class);
InventoryResponse result = null;
try {
result = inventoryClient.getInventoryItem("1");
} catch (RuntimeException e) {
assertEquals(CallNotPermittedException.class, e.getClass(), "Circuit breaker should prevent the request");
verifyNoInteractions(restTemplate, "Request should not be made when circuit breaker is open");
} finally {
assertNotNull(result, "Result should not be null");
assertEquals("default", result.id(), "Default item should be returned");
}
}
}

Source code: GitHub

Index
How to Implement PostgreSQL Full-Text Search with Spring Boot

15 min

Spring's transaction management with the @Transactional annotation

9 min

Spring Boot Rest APIs with PostgreSQL (Spring Boot + Rest APIs)

15 min

Caching in Spring Boot (@Cacheable, @CacheEvict & @CachePut)

21 min

Declarative REST Client in Spring Boot (Spring 6 HTTP Interface)

13 min

A Guide to Pact Contract Testing in Spring Boot Applications

10 min

Circuit Breaker in Spring Boot (Spring Cloud Circuit Breaker + Resilience4j)

12 min

Handling Concurrent Service Calls in a Spring Boot Application: CompletableFuture and @Async

11 min

Profiling a Spring Boot application with Pyroscope

7 min

Service discovery in Spring Boot (Spring Cloud + Netflix Eureka)

9 min

Dockerize Spring Boot app and Push image to DockerHub (Spring Boot + DockerHub)

4 min

Creating a Jenkins Pipeline for Spring Boot application

2 min

Circuit Breaker Pattern in Microservices (Spring BOOT + Resilience4j)

4 min

Monitoring Microservices (Spring Boot + Micrometer + Prometheus + Grafana)

7 min

Edge Server Pattern in Microservices (Spring Cloud Gateway)

7 min

Spring Cloud config server setup with Git

8 min

Distributed Tracing in Microservices (Spring Cloud Sleuth + Zipkin)

9 min

Circuit Breaker Pattern with Resilience4J in a Spring Boot Application

24 min

Deploying Spring Boot microservices on Kubernetes Cluster

12 min

Reactive programming in Java with Project Reactor

50 min

Spring Reactive with PostgreSQL (Spring Boot WebFlux + PostgreSQL)

13 min

Spring Reactive, Thymeleaf Hello World (Spring Webflux + Thymeleaf + JS/CSS)

9 min

Problem JSON (application/problem+json) in Spring WebFlux

15 min

Spring Boot Login/Logout (Spring Security + MySql + Thymeleaf)

21 min

Securing Server-to-Server Communication with "Spring Boot" & "OAuth 2"

18 min

Integrating AWS OpenSearch with Spring Boot (Index, Search, Pagination & Aggregation)

8 min

Integrating Elasticsearch with a Spring Boot and PostgreSQL application

16 min

Sending Emails in Spring Boot via SMTP

7 min

How to create a basic Spring 6 project using Maven

5 min

Spring Boot, Thymeleaf Hello World (Spring Boot + Thymeleaf + JS/CSS)

9 min