Resilience4j’s CircuitBreaker doesn’t actually stop requests from hitting your downstream services; it stops your application from sending those requests when a downstream service is failing.

Let’s see it in action. Imagine a simple REST endpoint in your Spring Boot app that calls an external service.

@RestController
public class ExternalServiceConsumer {

    private final RestTemplate restTemplate;

    public ExternalServiceConsumer(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @GetMapping("/call-external")
    public String callExternalService() {
        // In a real app, this would be a more complex URL and potentially a custom object
        return restTemplate.getForObject("http://localhost:8081/slow-service", String.class);
    }
}

Now, let’s add a Resilience4j CircuitBreaker to this. First, you’ll need the resilience4j-spring-boot3-starter (or resilience4j-spring-boot2-starter if you’re on an older Spring Boot version) and resilience4j-circuitbreaker dependencies.

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3-starter</artifactId>
    <version>2.1.0</version> <!-- Use the latest compatible version -->
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-circuitbreaker</artifactId>
    <version>2.1.0</version> <!-- Use the latest compatible version -->
</dependency>

Next, configure your CircuitBreaker in application.yml:

resilience4j.circuitbreaker:
  instances:
    externalService: # This is the name of our circuit breaker
      slidingWindowType: COUNT_BASED
      slidingWindowSize: 10 # Look at the last 10 calls
      failureRateThreshold: 50 # If 50% of calls fail, open the circuit
      waitDurationInOpenState: 5s # Stay open for 5 seconds
      permittedNumberOfCallsInHalfOpenState: 3 # Allow 3 calls in half-open state
      recordExceptions: # Exceptions that count as failures
        - org.springframework.web.client.HttpServerErrorException
        - org.springframework.web.client.ResourceAccessException

Now, wrap your call with the CircuitBreaker using the spring-cloud-starter-circuitbreaker-resilience4j integration. This starter provides a CircuitBreakerFactory that you can inject.

@RestController
public class ExternalServiceConsumer {

    private final RestTemplate restTemplate;
    private final CircuitBreakerFactory circuitBreakerFactory;

    public ExternalServiceConsumer(RestTemplate restTemplate, CircuitBreakerFactory circuitBreakerFactory) {
        this.restTemplate = restTemplate;
        this.circuitBreakerFactory = circuitBreakerFactory;
    }

    @GetMapping("/call-external")
    public String callExternalService() {
        CircuitBreaker circuitBreaker = circuitBreakerFactory.create("externalService"); // Name matches config

        // Use the decorated API to wrap the call
        return circuitBreaker.executeCallable(() ->
            restTemplate.getForObject("http://localhost:8081/slow-service", String.class)
        );
    }
}

When http://localhost:8081/slow-service starts returning errors (e.g., 5xx status codes, or connection refused), Resilience4j will start tracking them. If the failure rate crosses the failureRateThreshold (50% in our case) within the slidingWindowSize (10 calls), the CircuitBreaker transitions to the OPEN state.

Once OPEN, any subsequent calls to /call-external will immediately throw a CallNotPermittedException without even attempting to call http://localhost:8081/slow-service. This prevents your application from wasting resources on a failing dependency and allows the downstream service time to recover.

After waitDurationInOpenState (5 seconds), the CircuitBreaker transitions to HALF-OPEN. In this state, it allows permittedNumberOfCallsInHalfOpenState (3 calls) through. If these calls succeed, the CircuitBreaker returns to CLOSED. If any of them fail, it immediately goes back to OPEN for another waitDurationInOpenState.

The CircuitBreaker isn’t just about counting failures; it’s about managing the risk of making calls. By default, it tracks HttpServerErrorException and ResourceAccessException. You can add more specific exceptions to recordExceptions in your YAML to fine-tune what constitutes a "failure."

You can monitor the state of your circuit breakers via the Spring Boot Actuator endpoints. Add the actuator dependency:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

And then access /actuator/circuit-breakers (or /actuator/health/circuit-breakers for a health check view). You’ll see metrics like successfulCalls, failedCalls, slowCalls, and the current state (CLOSED, OPEN, HALF_OPEN).

The real power comes when you combine this with a RestTemplate that’s also configured to fail fast. If your RestTemplate has a connection timeout of 1 second and a read timeout of 1 second, and the downstream service is taking 10 seconds to respond, your RestTemplate will time out before Resilience4j even gets a chance to record a failure for that specific call in the circuit breaker’s window. However, if the service is consistently returning 500 errors, Resilience4j will catch those and open the circuit.

The CircuitBreaker doesn’t track "slow" calls by itself; it only tracks calls that result in a recorded exception. If you want to protect against slow responses that don’t error out but still hog resources, you’d typically combine this with a RateLimiter or a ThreadPoolExecutor with appropriate timeouts and queue sizes. However, the CircuitBreaker is specifically designed to prevent cascading failures caused by errors from a dependency.

Want structured learning?

Take the full Circuit-breaker course →