Build a circuit breaker with Resilience4j and Spring Boot
Most Spring Boot applications depend on services they do not control. A checkout can call a payment gateway, a fraud engine and an address service, so a slow provider can tie up request threads while customers wait. A circuit breaker gives those calls a controlled failure path and stops repeated requests to an unhealthy dependency.
Resilience4j brings the circuit breaker pattern to Spring Boot through annotations and a programmatic API. Its state machine moves from CLOSED to OPEN after enough failures, then permits a few trial calls in HALF_OPEN. Successful probes close the circuit; failed probes reopen it.
The implementation below uses Spring Boot 3, a REST payment call, YAML configuration and deliberate fallback behaviour. It also covers observability, testing and the operational details that matter for Australian deployments.
Scope the failure boundary
Apply the circuit breaker around a logical downstream operation, such as charging a card, rather than around an entire controller. A shared instance for every external call would open because one provider is unhealthy, even when another remains reliable.
Timeouts and retries are separate controls. A retry can increase load on a struggling provider, while retrying a non-idempotent charge can create duplicate payments. Use retries only where the operation is idempotent or the provider supports an idempotency key, and give each dependency its own breaker.
Configure the breaker for a payment provider
Add spring-boot-starter-aop and the Resilience4j Spring Boot module. This example targets Spring Boot 3; older projects should use the matching Resilience4j module and a compatible release.
Configure a named instance in application.yml:
resilience4j:
circuitbreaker:
instances:
paymentGateway:
slidingWindowType: COUNT_BASED
slidingWindowSize: 20
minimumNumberOfCalls: 10
failureRateThreshold: 50
slowCallDurationThreshold: 2s
slowCallRateThreshold: 80
waitDurationInOpenState: 30s
permittedNumberOfCallsInHalfOpenState: 3
automaticTransitionFromOpenToHalfOpenEnabled: true
registerHealthIndicator: true
recordExceptions:
- org.springframework.web.client.ResourceAccessException
- org.springframework.web.client.HttpServerErrorException
- java.util.concurrent.TimeoutException
The window counts recent calls, while the thresholds decide when the circuit opens. The open-state wait controls how long the service waits before allowing trial requests. Restricting recorded exceptions prevents ordinary client errors or application bugs from opening the circuit.
Handle failures with a fallback
Create a RestClient bean with the provider’s base URL, then inject it into the service. Annotate the operation and provide a fallback with matching arguments plus a Throwable:
@Service
public class PaymentService {
private final RestClient paymentClient;
public PaymentService(RestClient paymentClient) {
this.paymentClient = paymentClient;
}
@CircuitBreaker(
name = "paymentGateway",
fallbackMethod = "chargePaymentFallback"
)
public PaymentResponse chargePayment(PaymentRequest request) {
return paymentClient.post()
.uri("/charges")
.body(request)
.retrieve()
.body(PaymentResponse.class);
}
public PaymentResponse chargePaymentFallback(
PaymentRequest request, Throwable cause) {
return PaymentResponse.pending();
}
}
The fallback should express business truth. Returning a pending status makes sense if the request can be safely queued; it must not masquerade as a confirmed payment. Because Spring applies the annotation through a proxy, callers must enter through the service bean rather than invoke the method internally on the same object.
Make dependency failures visible
Add Spring Boot Actuator and expose the circuit-breaker endpoint alongside health and metrics:
management:
endpoints:
web:
exposure:
include: health,metrics,circuitbreakers
These endpoints expose call volume, state changes and latency information for dashboards and diagnostics. Log transitions with a dedicated logger, and include a correlation identifier in payment records so engineers can connect a failed charge with the exact provider request.
In an Australian support chat, “no worries, we’re on it” can keep a customer calm. The incident record still needs the provider name, circuit state and correlation identifier, especially when a brief regional mobile-network fault resembles a complete payment outage.
Test the failure transitions
Use MockRestServiceServer or WireMock to simulate provider behaviour. Arrange repeated server errors to move the circuit from CLOSED to OPEN, confirm later calls take the fallback, then wait beyond the configured open-state duration to exercise HALF_OPEN. A short test-specific wait duration keeps the suite quick.
Include slow responses, provider validation errors and successful recovery. A 4xx response should normally reach the caller without recording a dependency failure, while a timeout should count only when its exception type appears in the configuration.
Practical recommendations for resilient deployments
Compare the open-state duration with the provider’s actual recovery time before rollout. Australian traffic can cross Sydney, Melbourne and Perth, and regional fixed-line and mobile connections vary, so timeout budgets should tolerate normal network latency without hiding a genuine outage. Local payment operations also involve Australian-dollar amounts, GST-inclusive consumer totals and support handoffs across time zones affected by daylight-saving changes.
For an independent architecture or production-readiness review, JavaWhizz provides IT consultancy guidance alongside hands-on implementation support.
- Give each external dependency a separate circuit breaker with documented timeout and failure thresholds.
- Record infrastructure instability rather than invalid requests or application programming errors.
- Retry idempotent operations only, using provider idempotency keys for payment requests.
- Alert on circuit-open duration and failure rate instead of every individual rejected call.
- Rehearse fallback flows with support teams across Australian time zones and daylight-saving changes.