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)