Voltar ao laboratório
Tutorial Arquitetura 8 min

Circuit Breaker na Prática com Resilience4j

21 de maio de 2026

O Problema

Quando um serviço externo fica lento ou indisponível, sem Circuit Breaker, cada requisição ao seu serviço vai esperar pelo timeout — bloqueando threads e degradando toda a aplicação.

Seu serviço → Serviço externo (lento) → Thread bloqueada por 30s
              → Mais requisições chegam → Pool de threads esgota → Timeout geral

O Circuit Breaker interrompe esse ciclo: após N falhas consecutivas, ele "abre" e rejeita imediatamente as chamadas, sem esperar pelo timeout.


Dependência Maven

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

Configuração (application.yml)

resilience4j:
  circuitbreaker:
    instances:
      pagamentoService:
        # Abre o circuito após 50% de falha em uma janela de 10 chamadas
        slidingWindowSize: 10
        failureRateThreshold: 50
        # Espera 10s antes de tentar de novo (estado Half-Open)
        waitDurationInOpenState: 10s
        # Quantas chamadas teste no estado Half-Open
        permittedNumberOfCallsInHalfOpenState: 3
        # Exceções que contam como falha
        recordExceptions:
          - java.io.IOException
          - java.util.concurrent.TimeoutException
          - feign.FeignException

Implementação com Anotação

@Service
public class PagamentoService {

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

    // Fallback: retorna resposta degradada quando o circuito está aberto
    private PagamentoResponse fallbackPagamento(PagamentoRequest request, Exception ex) {
        log.warn("Circuit aberto para adquirente. Motivo: {}", ex.getMessage());
        return PagamentoResponse.builder()
            .status("PENDING")
            .mensagem("Sistema temporariamente indisponível. Tente em instantes.")
            .build();
    }
}

Implementação Programática (mais controle)

@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)
        );
    }
}

Estados do Circuit Breaker

| Estado | Comportamento | |--------|--------------| | Closed | Normal — todas as chamadas passam | | Open | Circuito aberto — rejeita imediatamente, vai para fallback | | Half-Open | Tentativa — deixa passar N chamadas de teste |


Monitoramento com Actuator

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

Endpoint: GET /actuator/health exibe o estado de cada circuit breaker.


Combinando com Retry

O Retry deve ser aplicado antes do Circuit Breaker (mais interno):

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

Resilience4j aplica a ordem correta automaticamente: Retry → CircuitBreaker → Bulkhead → TimeLimiter.


Quando NÃO usar Circuit Breaker

  • Chamadas para banco de dados próprio (use connection pool limits)
  • Operações idempotentes de baixo custo com retry barato
  • Quando o fallback cria inconsistência de dados (ex: saldo incorreto)

Referências

Circuit Breaker na Prática com Resilience4j — APCosta Lab — APCosta