Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 65

Implementing Circuit Breaker with Resilience4j

Recommended resources

Hello! Welcome to the first lesson in our module on Microservices: Resilience & Observability.

In our previous module, we built a distributed transaction using the Saga pattern. We saw how services can collaborate through asynchronous events. But this introduces a new risk: what happens if one of the services in the chain, like the Payment Service, becomes slow or completely unresponsive? The calling service, our Order Service, might waste critical resources (like threads) waiting for a response that will never come. If enough requests pile up, the Order Service itself can fail, causing a cascading failure that ripples through the entire system.

Today, we'll tackle this exact problem. Your learning outcome is to implement the Circuit Breaker pattern using Resilience4j to prevent cascading failures. This is a fundamental resilience pattern and a very common topic in senior-level system design and coding interviews.

1. Understanding the Circuit Breaker Pattern

At its core, a circuit breaker is a proxy for operations that might fail, like a network call to another microservice. It monitors for failures and, if they reach a certain threshold, it "trips" or "opens the circuit," preventing further calls to the failing service for a set period. This has two key benefits:

  1. Protects the client: The calling service doesn't get stuck waiting for a failing dependency. It can "fail fast" and immediately execute a fallback action, like returning a default response or reading from a cache.
  2. Protects the dependency: The failing service is given a grace period to recover without being hammered by continuous requests.

A circuit breaker operates as a state machine with three main states.

Circuit Breaker using Resilience4j in Spring Boot
This diagram shows the three states of a circuit breaker. It starts in `CLOSED`, moves to `OPEN` on repeated failures, transitions to `HALF_OPEN` after a timeout, and returns to `CLOSED` upon successful recovery.

The states are:

  • CLOSED: This is the default state. Requests are allowed to pass through to the dependent service. The circuit breaker monitors the number of failures. If the failure rate exceeds a configured threshold (e.g., 50% of the last 20 calls failed), it transitions to the OPEN state.
  • OPEN: The circuit is "tripped." For a configured duration (the waitDurationInOpenState), all calls to the dependent service are immediately rejected with an exception. The circuit breaker doesn't even attempt the call. This is where you would provide a fallback response. After the wait duration expires, the circuit breaker transitions to HALF_OPEN.
  • HALF_OPEN: In this state, the circuit breaker allows a limited number of "trial" requests to pass through to the dependent service.
    • If these trial requests succeed, the circuit breaker assumes the service has recovered and transitions back to the CLOSED state.
    • If any of these trial requests fail, it assumes the service is still unavailable and transitions back to the OPEN state, restarting the wait timer.
Test your understanding!

Imagine you have two services, Service A and Service B. Service A calls an endpoint on Service B. Service B is currently down. If Service A uses a circuit breaker to protect this call, what state will the circuit breaker be in after several failed calls? What will happen to new requests coming into Service A that need to call Service B?

Show answer

After several consecutive failures, the circuit breaker's failure threshold will be exceeded, and it will transition from the CLOSED state to the OPEN state.

Once the circuit is OPEN, new requests coming into Service A will not even attempt to call Service B. Instead, they will be rejected immediately by the circuit breaker, and Service A should execute its fallback logic (e.g., returning a cached response or a user-friendly error message). This prevents Service A from wasting resources on a known-to-be-failing dependency.


2. Implementing a Circuit Breaker with Resilience4j

Resilience4j is the de-facto standard library for implementing resilience patterns in modern Java applications, especially with Spring Boot. It's lightweight, modular, and designed with functional programming in mind.

We'll now walk through the steps to add a circuit breaker to a Spring Boot service.

Microservice | Resilience4J Circuit Breaker Implementation on Spring Boot | JavaTechie

To see a full, hands-on implementation, let's watch this tutorial from the Java Techie channel. It will guide you through setting up a project, adding dependencies, applying the annotation, and configuring the circuit breaker.

Watch the video, paying attention to these key segments: Introduction (00:00 - 04:51): Understand the problem of cascading failures and get a conceptual overview of the three circuit breaker states. Implementation (09:41 - 17:03): Focus on how to add the dependencies (pom.xml), apply the @CircuitBreaker annotation, define a fallbackMethod, and configure the properties in application.yml. Demonstration (17:03 - 24:32): Observe how to simulate a service failure and use the actuator's /health endpoint to see the circuit breaker transition from CLOSED to OPEN, then HALF-OPEN, and finally back to CLOSED. Note: The video uses an older dependency (resilience4j-spring-boot-2). For a modern Spring Boot 3 project, you would use resilience4j-spring-boot3. The concepts and configuration keys remain the same.

Let's break down the key steps from the video into a clear, interview-ready summary.

Step 1: Add Dependencies

You need three key dependencies in your pom.xml:

<!-- Resilience4j for Spring Boot 3 -->
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
</dependency>

<!-- Spring AOP for aspect-based implementation -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!-- Spring Actuator to monitor the circuit breaker's state -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • Resilience4j: The core library.
  • AOP: Allows Spring to intercept method calls and apply the circuit breaker logic via annotations.
  • Actuator: Exposes management endpoints, including /actuator/health, which shows the status of your circuit breakers.

Step 2: Annotate the Method

On the method that makes the remote call, add the @CircuitBreaker annotation.

@Service
public class ExternalApiService {

    private final RestTemplate restTemplate;

    // ... constructor ...

    @CircuitBreaker(name = "downstreamService", fallbackMethod = "getFallbackResponse")
    public String callDownstreamService() {
        // This call might fail
        return restTemplate.getForObject("http://downstream-service/api/data", String.class);
    }

    // Fallback method
    public String getFallbackResponse(Throwable t) {
        // Log the error: t.getMessage()
        return "Service is currently unavailable. Please try again later.";
    }
}
  • name: This is the name of the circuit breaker instance. It links the annotation to the configuration in your application.yml.
  • fallbackMethod: This specifies the name of the method to call if the circuit is OPEN or if the initial call fails. The fallback method must have a compatible signature. It can optionally accept a Throwable argument to log the exception that caused the failure.

Step 3: Configure in application.yml

This is where you tune the behavior of your circuit breaker. The configuration is hierarchical, under resilience4j.circuitbreaker.instances.<your-instance-name>.

CircuitBreaker

The application.yml file is where you define the specific behavior of your circuit breaker. For a detailed list of all available properties and their default values, the official Resilience4j documentation is the best resource. Please review the main configuration table.

Focus on the table under the 'Create and configure a CircuitBreaker' section. Pay close attention to the properties we're about to discuss: failureRateThreshold, slidingWindowSize, minimumNumberOfCalls, waitDurationInOpenState, and permittedNumberOfCallsInHalfOpenState.

Here's a typical configuration with explanations:

resilience4j:
  circuitbreaker:
    instances:
      downstreamService: # Must match the 'name' in @CircuitBreaker
        # How many failures before tripping? (in %)
        failureRateThreshold: 50
        # What's the time/call window for calculating the failure rate?
        slidingWindowType: COUNT_BASED
        slidingWindowSize: 20
        # How many calls are needed before the failure rate is calculated?
        minimumNumberOfCalls: 10
        # How long to wait in the OPEN state before trying again?
        waitDurationInOpenState: 10s
        # How many trial requests to allow in the HALF_OPEN state?
        permittedNumberOfCallsInHalfOpenState: 3

management: # To expose the circuit breaker state via actuator
  endpoints:
    web:
      exposure:
        include: health
  endpoint:
    health:
      show-details: always

This configuration means:

  • The circuit breaker monitors the last 20 calls (slidingWindowSize).
  • It will only start calculating the failure rate after at least 10 calls have been made (minimumNumberOfCalls).
  • If 50% or more of those calls fail (failureRateThreshold), it will trip and move to the OPEN state.
  • It will stay OPEN for 10 seconds (waitDurationInOpenState), rejecting all calls.
  • After 10 seconds, it will move to HALF_OPEN and allow 3 trial requests through (permittedNumberOfCallsInHalfOpenState).

3. Interview Focus: Tuning and Trade-offs

Simply knowing how to implement a circuit breaker isn't enough for a senior role interview. You need to explain why you chose certain configurations.

Test your understanding! (Interview Question)

An interviewer asks: "You've configured a circuit breaker. How would you decide on the values for failureRateThreshold and waitDurationInOpenState in a production environment? What are the trade-offs?"

Show answer

This is a great question about practical application. A strong answer would cover these points:

"The values for these properties depend heavily on the business context and the service's characteristics. There is no one-size-fits-all answer.

  1. failureRateThreshold:

    • Trade-off: A low threshold (e.g., 20%) makes the circuit very sensitive and might trip due to transient network blips, causing unnecessary fallbacks. A high threshold (e.g., 80%) might not trip fast enough to protect the system during a real outage.
    • Tuning Strategy: A good starting point is often 50%. You should analyze the service's typical error rate under normal conditions. If the baseline error rate is already 5%, setting the threshold to 10% would be too sensitive. It should be set significantly above the normal operational error rate but low enough to detect a genuine problem quickly.
  2. waitDurationInOpenState:

    • Trade-off: A short duration (e.g., 5 seconds) allows for a quick recovery attempt but might not give a struggling service enough time to restart or scale up, causing it to be hammered again too soon. A long duration (e.g., 60 seconds) gives the downstream service plenty of recovery time but means users will experience the fallback behavior for longer, even if the service recovers quickly.
    • Tuning Strategy: The duration should be based on the expected recovery time of the downstream service. If it's a service that typically restarts in 15-20 seconds, a waitDurationInOpenState of 30 seconds is a reasonable starting point. For critical services where user impact is high, you might want a shorter duration combined with more sophisticated fallback logic.

Ultimately, these values should be set based on observability data (metrics on failure rates and recovery times) and then fine-tuned over time as you monitor the system's behavior in production."


Conclusion

In this lesson, you learned how to safeguard your microservices from cascading failures using one of the most important resilience patterns. You can now not only implement a circuit breaker with Resilience4j but also justify your configuration choices—a key skill for building robust, production-grade systems.

Key Takeaways:

  • Cascading failures are a major risk in microservice architectures.
  • The Circuit Breaker pattern prevents this by isolating failing dependencies, allowing them to recover while protecting the client system.
  • A circuit breaker operates with three states: CLOSED, OPEN, and HALF_OPEN.
  • Resilience4j integrated with Spring Boot makes implementation straightforward using the @CircuitBreaker annotation and application.yml configuration.
  • Tuning circuit breaker parameters is a critical skill that involves balancing sensitivity against stability, based on the specific service's requirements and historical performance.

Preview of the Next Lesson:
Our circuit breaker currently uses a very simple fallback: returning a hardcoded string. In a real system, you'd want more sophisticated behavior. Furthermore, what about failures that are transient and might succeed on a second try? The next lesson will address both of these points as we learn to implement fallback strategies and retry mechanisms with exponential backoff.