Back to lab
Tutorial Architecture 8 min

Circuit Breaker in Practice with Resilience4j

May 21, 2026

The Problem

When an external service becomes slow or unavailable, without a Circuit Breaker every incoming request to your service will wait for the timeout — blocking threads and degrading the entire application.

Your service → External service (slow) → Thread blocked for 30s
              → More requests arrive → Thread pool exhausted → General timeout

The Circuit Breaker breaks this cycle: after N consecutive failures, it "opens" and immediately rejects calls without waiting for the timeout.


Maven Dependency

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
    <version>2.2.0</version>
</dependency>

Configuration (application.yml)

resilience4j:
  circuitbreaker:
    instances:
      pagamentoService:
        # Opens the circuit after 50% failure rate in a window of 10 calls
        slidingWindowSize: 10
        failureRateThreshold: 50
        # Waits 10s before retrying (Half-Open state)
        waitDurationInOpenState: 10s
        # Number of test calls allowed in Half-Open state
        permittedNumberOfCallsInHalfOpenState: 3
        # Exceptions counted as failures
        recordExceptions:
          - java.io.IOException
          - java.util.concurrent.TimeoutException
          - feign.FeignException

Annotation-Based Implementation

@Service
public class PagamentoService {

    @CircuitBreaker(name = "pagamentoService", fallbackMethod = "fallbackPagamento")
    public PagamentoResponse processar(PagamentoRequest request) {
        return adquirenteClient.processar(request);
    }

    // Fallback: returns a degraded response when the circuit is open
    private PagamentoResponse fallbackPagamento(PagamentoRequest request, Exception ex) {
        log.warn("Circuit open for acquirer. Reason: {}", ex.getMessage());
        return PagamentoResponse.builder()
            .status("PENDING")
            .mensagem("Sistema temporariamente indisponível. Tente em instantes.")
            .build();
    }
}

Programmatic Implementation (more control)

@Service
public class PagamentoService {

    private final CircuitBreaker circuitBreaker;
    private final AdquirenteClient adquirenteClient;

    public PagamentoService(CircuitBreakerRegistry registry, AdquirenteClient client) {
        this.circuitBreaker = registry.circuitBreaker("pagamentoService");
        this.adquirenteClient = client;
    }

    public PagamentoResponse processar(PagamentoRequest request) {
        return circuitBreaker.executeSupplier(
            () -> adquirenteClient.processar(request)
        );
    }
}

Circuit Breaker States

| State | Behavior | |-------|----------| | Closed | Normal — all calls pass through | | Open | Circuit open — immediately rejects calls, routes to fallback | | Half-Open | Probing — allows N test calls through |


Monitoring with Actuator

management:
  endpoints:
    web:
      exposure:
        include: health,circuitbreakers
  health:
    circuitbreakers:
      enabled: true

Endpoint: GET /actuator/health displays the state of each circuit breaker.


Combining with Retry

Retry should be applied before the Circuit Breaker (innermost decorator):

@Retry(name = "pagamentoService", fallbackMethod = "fallbackPagamento")
@CircuitBreaker(name = "pagamentoService", fallbackMethod = "fallbackPagamento")
public PagamentoResponse processar(PagamentoRequest request) {
    return adquirenteClient.processar(request);
}

Resilience4j applies the correct order automatically: Retry → CircuitBreaker → Bulkhead → TimeLimiter.


When NOT to Use a Circuit Breaker

  • Calls to your own database (use connection pool limits instead)
  • Idempotent, low-cost operations with cheap retries
  • When the fallback creates data inconsistency (e.g., incorrect balance)

References

Circuit Breaker in Practice with Resilience4j — APCosta Lab — APCosta