Create your own
Lesson illustration

Circuit Breaker with Resilience4j Fallback

Hello! Welcome to the next lesson in our module on Inter-Service Communication Patterns.

In our last lesson, we built an API Gateway, which acts as a robust entry point for all client requests. We configured it for routing, transformation, and even rate limiting to protect our backend services. However, this raises a critical question for production readiness: what happens if one of those backend services fails? The gateway, or any other service calling it, will continue to send requests, consuming resources (like threads and connections) while waiting for a response that will never come. This can trigger a chain reaction, bringing down the calling service and potentially the entire system—a scenario known as a cascading failure.

Today, we will address this vulnerability head-on. By the end of this lesson, you will be able to implement the Circuit Breaker pattern with a fallback mechanism using Resilience4j to prevent cascading failures. This pattern is a fundamental building block for creating resilient, fault-tolerant systems and is a key topic in senior-level system design interviews.

1. The Problem: Cascading Failures

Before implementing a solution, let's fully grasp the problem it solves. A cascading failure is when a failure in one component of a system triggers a sequence of failures in other dependent components.

To see a concrete example, let's watch a short explanation that uses a typical e-commerce platform.

Circuit Breaker Pattern in Microservices

The video 'Circuit Breaker Pattern in Microservices' from the ByteMonk channel provides a clear illustration of how a single service outage can ripple through an entire system.

Watch the segment from 03:44 to 05:49. Pay close attention to how the failure of the 'Product Service' cascades to the 'Shopping Cart Service', 'Order Service', and 'Recommendation Service'.

As the video showed, without a protective mechanism, a single point of failure can lead to a system-wide outage. The Circuit Breaker pattern is designed to prevent exactly this by acting like an electrical circuit breaker: when it detects a persistent fault, it "trips" and stops the flow of requests to the failing service, giving it time to recover.

2. The Circuit Breaker State Machine

A software circuit breaker doesn't just block calls; it intelligently manages them through a state machine with three states: CLOSED, OPEN, and HALF-OPEN.

Circuit Breaker using Resilience4J in Spring Boot
This diagram shows the three states of the circuit breaker and the flow of control. When the circuit is CLOSED, requests pass through. After enough failures, it moves to the OPEN state, where requests are blocked and a fallback is used. After a timeout, it enters the HALF-OPEN state to test for recovery.

To understand the transitions between these states, let's watch a detailed walkthrough.

Microservices Java Spring Boot Resilience4J Tutorial | Circuit Breaker Pattern | Spring Cloud

The video 'Microservices Java Spring Boot Resilience4J Tutorial' provides an excellent explanation of the circuit breaker's state machine.

Watch the segment from 06:58 to 09:08. Focus on understanding the conditions that cause the state to change: CLOSED to OPEN: When the failure rate exceeds a configured threshold. OPEN to HALF-OPEN: After a configured 'wait duration' passes. HALF-OPEN to CLOSED: If a permitted number of test calls succeed. HALF-OPEN to OPEN: If the test calls fail.

This state machine allows a service to stop hammering a failing dependency while also periodically checking if that dependency has recovered, enabling automatic healing.

3. Implementation with Resilience4j

Netflix Hystrix was the original, popular library for this pattern, but it's no longer in active development. The modern, recommended choice is Resilience4j, a lightweight, modular fault-tolerance library that integrates seamlessly with Spring Boot.

Let's walk through the implementation step-by-step.

Step 1: Add Dependencies

To use Resilience4j with Spring Boot's annotation-driven approach, you need two dependencies in your pom.xml.

Implementing Circuit Breaker with Resilience4j in Spring ...

The article 'Implementing Circuit Breaker with Resilience4j in Spring Boot' clearly lists the necessary dependencies. Let's review them.

Read the 'Project Setup and Dependencies' section. Note the two required dependencies: resilience4j-spring-boot2 and spring-boot-starter-aop.

As the article mentions, spring-boot-starter-aop is crucial. Resilience4j uses Aspect-Oriented Programming (AOP) to wrap your methods with the circuit breaker logic. The @CircuitBreaker annotation wouldn't work without it.

Step 2: Configure the Circuit Breaker

Configuration is done in your application.yml file. This is where you define the behavior of your circuit breaker instances, tuning the parameters we saw in the state machine diagram.

Implementing Circuit Breaker with Resilience4j in Spring ...

The same article provides a great breakdown of the core configuration properties. Understanding these is key for interview discussions about tuning system resilience.

Read the 'Basic Configuration for Circuit Breaker' section. Focus on what each of these properties controls: slidingWindowType and slidingWindowSize: Defines the window of recent calls to evaluate. minimumNumberOfCalls: The number of calls needed before the circuit breaker starts calculating the failure rate. failureRateThreshold: The percentage of failed calls that will trip the circuit to OPEN. waitDurationInOpenState: How long the circuit stays OPEN before transitioning to HALF-OPEN. permittedNumberOfCallsInHalfOpenState: The number of test requests allowed in the HALF-OPEN state.

Here is an example configuration for a service call named productService:

resilience4j:
  circuitbreaker:
    instances:
      productService:
        registerHealthIndicator: true
        slidingWindowType: COUNT_BASED
        slidingWindowSize: 20
        minimumNumberOfCalls: 10
        failureRateThreshold: 50 # If 50% of the last 20 calls (once 10 have been made) fail, open the circuit.
        waitDurationInOpenState: 10s # Wait 10 seconds in the OPEN state.
        permittedNumberOfCallsInHalfOpenState: 5 # Allow 5 test calls in HALF-OPEN state.
        slowCallRateThreshold: 60
        slowCallDurationThreshold: 2s
        ignoreExceptions: # An important configuration for production!
          - com.example.exceptions.BusinessValidationException

Interview Pro-Tip: The ignoreExceptions property is a powerful feature to discuss. It allows you to specify exceptions that should not count as failures. For example, you wouldn't want a 400 Bad Request or a custom validation exception to trip the circuit breaker, as these indicate a client-side error, not a service failure. The fault lies with the caller, not the dependency.

Step 3: Apply the @CircuitBreaker Annotation and Define a Fallback

Now, you apply the circuit breaker to the method that makes the remote call.

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class ProductServiceClient {

    private final RestTemplate restTemplate;

    // ... constructor ...

    @CircuitBreaker(name = "productService", fallbackMethod = "getProductFallback")
    public Product getProductById(String productId) {
        // This is the call that might fail
        return restTemplate.getForObject("http://product-service/products/" + productId, Product.class);
    }

    // This is the fallback method
    public Product getProductFallback(String productId, Throwable t) {
        // Log the error for observability
        log.error("Fallback for getProductById, productId: {}, error: {}", productId, t.getMessage());
        // Return cached data or a default response.
        return new Product(productId, "Default Product", "N/A");
    }
}

There are two key parts here:

  1. @CircuitBreaker(name = "productService", ...): The name attribute links this code to the productService configuration block in your application.yml.
  2. fallbackMethod = "getProductFallback": This specifies which method to call if the circuit is OPEN or if the initial call throws an exception.

The fallback method signature is important:

  • It must have the same return type as the original method.
  • It can accept the same parameters as the original method, plus an optional Throwable parameter to log the error that triggered the fallback.

Advanced Fallback Strategy: Handling Different Exceptions

For a senior role, you should be able to discuss more nuanced error handling. What if you want to fail over to a secondary service for a 503 Service Unavailable but simply re-throw the exception for a 404 Not Found? You can achieve this by creating multiple, overloaded fallback methods.

Failover and Circuit Breaker with Resilience4j

The article 'Failover and Circuit Breaker with Resilience4j' from Lydtech Consulting provides an excellent code example of this advanced technique.

Read the 'Failover' section. Pay close attention to how they define two lookupAccountFallback methods. One handles HttpClientErrorException (for 4xx errors) by re-throwing it, while the other handles a generic Throwable (for 5xx or other transient errors) by calling a secondary service. This is a very practical, production-ready pattern.

This ability to route to different fallbacks based on the error type is a powerful way to build more intelligent and resilient systems.

4. Putting It All Together: A Live Demo

Theory is great, but seeing the circuit breaker in action solidifies the concept. The following video demonstrates everything we've discussed: dependencies, configuration, code, and—most importantly—testing the behavior.

Microservices Java Spring Boot Resilience4J Tutorial | Circuit Breaker Pattern | Spring Cloud

Let's return to the 'Microservices Java Spring Boot Resilience4J Tutorial' to watch a full end-to-end demonstration. This will show you how to verify the circuit breaker's state.

Watch the detailed demo from 22:16 to 28:40. Observe the following sequence of events: The system works correctly when all services are up. When a dependency is shut down, the fallback response is returned immediately. Repeated calls cause the circuit breaker's health endpoint (/actuator/health) to show increasing failedCalls. Once the threshold is met, the state changes from CLOSED to OPEN. After the wait duration, the state becomes HALF_OPEN. When the dependency is brought back up, calls in the HALF_OPEN state succeed, and the circuit returns to CLOSED.

The use of the actuator health endpoint is critical for observability. In a real system, you would plug these metrics into a monitoring dashboard (like Prometheus and Grafana) to track the health of your inter-service communication.

Test your understanding!

You are building an inventory-service that calls a supplier-service to check stock levels. The checkStock method in your SupplierClient is critical.

Requirements:

  1. Protect the checkStock method with a circuit breaker named supplierStockCheck.
  2. The circuit should open if 30% of the last 50 calls fail. However, it should only start calculating after at least 20 calls have been made.
  3. When the circuit is open, it should wait for 20 seconds before transitioning to the half-open state.
  4. If the call fails, a fallback method getStockFromCache should be invoked. This method should return a StockInfo object and log the error.
  5. Client-side 4xx errors, represented by a custom InvalidRequestException, should not contribute to the failure count.

Write the application.yml configuration and the Java code snippet (the SupplierClient class) to implement this.

Show answer

application.yml configuration:

resilience4j:
  circuitbreaker:
    instances:
      supplierStockCheck:
        registerHealthIndicator: true
        slidingWindowType: COUNT_BASED
        slidingWindowSize: 50
        minimumNumberOfCalls: 20
        failureRateThreshold: 30
        waitDurationInOpenState: 20s
        ignoreExceptions:
          - com.yourcompany.exceptions.InvalidRequestException

Java Code (SupplierClient.java):

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class SupplierClient {

    private static final Logger log = LoggerFactory.getLogger(SupplierClient.class);
    // Assume RestTemplate or FeignClient is injected here

    @CircuitBreaker(name = "supplierStockCheck", fallbackMethod = "getStockFromCache")
    public StockInfo checkStock(String itemId) {
        // Logic to call the external supplier-service
        // This call might throw an exception (e.g., RestClientException)
        // or an InvalidRequestException
        log.info("Checking stock for item: {}", itemId);
        // ... restTemplate.getForObject(...)
        return new StockInfo(itemId, 100); // Dummy success response
    }

    public StockInfo getStockFromCache(String itemId, Throwable throwable) {
        log.warn("Fallback: could not fetch stock for item {}. Returning cached/default data. Error: {}", 
                 itemId, throwable.getMessage());
        // Logic to retrieve the last known stock level from a cache (e.g., Redis)
        // For this example, we return a default object indicating zero stock.
        return new StockInfo(itemId, 0, "cached");
    }
}

Conclusion

You have successfully learned how to implement one of the most important resilience patterns in microservices. By preventing cascading failures, the Circuit Breaker pattern is essential for building stable, production-grade systems that can withstand partial outages.

Key Takeaways:

  • Purpose: The Circuit Breaker pattern prevents cascading failures by isolating failing services.
  • States: It operates using a CLOSED, OPEN, and HALF-OPEN state machine to stop calls to a failing service and periodically check for its recovery.
  • Resilience4j: The modern, lightweight library for implementing this in Spring Boot.
  • Implementation:
    1. Add resilience4j-spring-boot2 and spring-boot-starter-aop dependencies.
    2. Configure your circuit breaker instances in application.yml, tuning parameters like failureRateThreshold and waitDurationInOpenState.
    3. Apply the @CircuitBreaker annotation to the method making the remote call, linking it to your configuration and a fallbackMethod.
    4. Implement a meaningful fallback that provides a graceful user experience (e.g., returns cached data or a default response).
  • Advanced Tip: Use overloaded fallback methods and ignoreExceptions for more granular, production-ready error handling.

Next Up

The Circuit Breaker pattern is excellent for handling persistent or long-lasting faults where a service is completely down. But what about temporary, fleeting issues like a brief network glitch or a momentary server overload? Constantly opening the circuit for these transient faults might be too aggressive. For these scenarios, a simpler pattern is often more appropriate.

In our next lesson, we will explore another core feature of Resilience4j: implementing the Retry pattern with exponential backoff and jitter to handle transient faults.

Can't find a good explanation? Sign up and we'll make it for you

Sign up