Why Circuit breaker?
Circuit breaker Prevents cascading failures across microservices and Improves user experience by failing fast instead of hanging. Supports graceful degradation with fallbacks (e.g., cached responses, safe defaults).
┌───────────────┐
│ │
│ CLOSED │ ←─── Successes reset failure count
│ (Normal flow) │
└───────┬───────┘
│
│ Too many failures
▼
┌───────────────┐
│ │
│ OPEN │ ←─── All calls blocked, fallback used
│ (Protection) │
└───────┬───────┘
│
│ Wait duration expires
▼
┌───────────────┐
│ │
│ HALF-OPEN │ ←─── Limited test calls allowed
│ (Trial mode) │
└───────┬───────┘
│
┌───────┴───────────────┐
│ │
▼ ▼
Success → CLOSED Failures → back to OPEN
How Circuit breaker Works?
It cycles through three states — Closed, Open, and Half‑Open — to balance resilience with recovery.
Closed State – Requests flow normally to the downstream service, tracks metrics like error rate, timeouts, and latency.
Open State – Triggered when failures exceed the configured threshold, breaker “trips” and immediately rejects further calls.This prevents wasted retries and protects system resources.
Half‑Open State – After a cooldown period, the breaker allows limited trial requests. If these succeed → breaker resets to Closed. If they fail → breaker returns to Open for another cooldown.
What is the Default condition of Circuit breaker to move to open state?
For Resilence4j Failure Rate Threshold: 50% with a sliding window size of 100 Request. I.E. If 50 calls of last 100 request fails then circuit breaker moves to open state. Wait Duration in Open State: 60 seconds. After 60 Seconds Circuit breaker comes to half open state with 10 trial calls to test recovery before deciding to close or reopen state.
Sample config of Circuit Breaker which has sliding window of size 8 and failure call limit of 4. The CB would be OPEN state for 2 Seconds and permits 3 calls in HALFOPEN before turning to CLOSED state
resilience4j.circuitbreaker.instances.EmployeeService.registerHealthIndicator=true resilience4j.circuitbreaker.instances.EmployeeService.slidingWindowSize=8 resilience4j.circuitbreaker.instances.EmployeeService.failureRateThreshold=4 resilience4j.circuitbreaker.instances.EmployeeService.waitDurationInOpenState=2s resilience4j.circuitbreaker.instances.EmployeeService.permittedNumberOfCallsInHalfOpenState=3
From postman 20 request sent with delay interval of 0.3 seconds
| Count | Request | URL | Seconds | Http Status | CB State | Comments |
|---|---|---|---|---|---|---|
| 1 | Req1 | http://localhost:8085/empmgmt/test/employees/ise | 0.3 | 500 Server Error | CLOSED | |
| 2 | Req2 | http://localhost:8085/empmgmt/test/employees/ise | 0.6 | 500 Server Error | CLOSED | |
| 3 | Req3 | http://localhost:8085/empmgmt/test/employees/ise | 0.9 | 500 Server Error | CLOSED | |
| 4 | Req4 | http://localhost:8085/empmgmt/test/employees/ise | 1.2 | 500 Server Error | CLOSED | |
| 5 | Req5 | http://localhost:8085/empmgmt/test/employees/ise | 1.5 | 500 Server Error | CLOSED | |
| 6 | Req6 | http://localhost:8085/empmgmt/test/employees/ise | 1.8 | 500 Server Error | CLOSED | |
| 7 | Req7 | http://localhost:8085/empmgmt/test/employees/ise | 2.1 | 500 Server Error | CLOSED | |
| 8 | Req8 | http://localhost:8085/empmgmt/test/employees/ise | 2.4 | Fallback Method | OPEN | CB Opens as Sliding Window Size is 8 Request |
| 9 | Req9 | http://localhost:8085/empmgmt/test/employees | 2.7 | Fallback Method | OPEN | |
| 10 | Req10 | http://localhost:8085/empmgmt/test/employees | 3 | Fallback Method | OPEN | |
| 11 | Req11 | http://localhost:8085/empmgmt/test/employees | 3.3 | Fallback Method | OPEN | |
| 12 | Req12 | http://localhost:8085/empmgmt/test/employees | 3.6 | Fallback Method | OPEN | |
| 13 | Req13 | http://localhost:8085/empmgmt/test/employees | 3.9 | 201 OK | HALF-OPEN | CB comes to half open after waiting 2 Seconds |
| 14 | Req14 | http://localhost:8085/empmgmt/test/employees | 4.2 | 201 OK | HALF-OPEN | |
| 15 | Req15 | http://localhost:8085/empmgmt/test/employees | 4.5 | 201 OK | HALF-OPEN | CB test 3 Request in HALF OPEN State |
| 16 | Req16 | http://localhost:8085/empmgmt/test/employees | 4.8 | 201 OK | CLOSED | CB comes to CLOSED State if 3 Request are Successful |
| 17 | Req17 | http://localhost:8085/empmgmt/test/employees | 5.1 | 201 OK | CLOSED | |
| 18 | Req18 | http://localhost:8085/empmgmt/test/employees | 5.4 | 201 OK | CLOSED | |
| 19 | Req19 | http://localhost:8085/empmgmt/test/employees | 5.7 | 201 OK | CLOSED | |
| 20 | Req20 | http://localhost:8085/empmgmt/test/employees | 6 | 201 OK | CLOSED |
How it works
- @Autowired private CircuitBreakerRegistry circuitBreakerRegistry;
Spring injects the registry that holds all configured circuit breakers. You can fetch a breaker by name from here. - @PostConstruct
This method runs automatically after the bean is initialized, ensuring your event listeners are registered before the service starts handling requests. - circuitBreakerRegistry.circuitBreaker(EMPLOYEE_SERVICE);
Retrieves the circuit breaker instance named EMPLOYEE_SERVICE. That name must match what you configured in application.yml.
EmployeeService.java
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@GetMapping("/employees")
public String getEmployees() {
ResponseEntity<String> response = employeeService.getEmployeeDetails();
return response.getBody();
}
}
EmployeeService.java
@Service
@Slf4j
public class EmployeeService {
private final RestTemplate restTemplate;
private static final String EMPLOYEE_SERVICE = "EmployeeService";
AtomicInteger count = new AtomicInteger(1);
@Autowired
public EmployeeService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Autowired
private CircuitBreakerRegistry circuitBreakerRegistry;
@PostConstruct
public void registerCircuitBreakerEvents() {
// Retrieve the CircuitBreaker instance from the registry
// The name EMPLOYEE_SERVICE must match the one defined in application.yml
io.github.resilience4j.circuitbreaker.CircuitBreaker cb =
circuitBreakerRegistry.circuitBreaker(EMPLOYEE_SERVICE);
// Attach a listener to the CircuitBreaker’s event publisher
// This listener will be triggered whenever the breaker changes state
cb.getEventPublisher()
.onStateTransition(event -> {
// Cast the generic event to a specific CircuitBreakerOnStateTransitionEvent
CircuitBreakerOnStateTransitionEvent transitionEvent =
(CircuitBreakerOnStateTransitionEvent) event;
// Check if the breaker has moved to the OPEN state
if (transitionEvent.getStateTransition().getToState() ==
io.github.resilience4j.circuitbreaker.CircuitBreaker.State.OPEN) {
// Log a warning when the breaker trips OPEN (calls are blocked)
log.warn("⚠️ CircuitBreaker [{}] moved to OPEN state at {}",
cb.getName(), transitionEvent.getCreationTime());
} else {
// Log info for other transitions (CLOSED → HALF_OPEN, HALF_OPEN → CLOSED, etc.)
log.info("CircuitBreaker [{}] transitioned: {}",
cb.getName(), transitionEvent.getStateTransition());
}
});
}
// Use the annotation here
@io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker(name = EMPLOYEE_SERVICE, fallbackMethod = "getEmployeeFallback")
public ResponseEntity<String> getEmployeeDetails() {
String url = "http://localhost:8085/empmgmt/test/employees?delay=500";
if(count.get() <=8){
url = "http://localhost:8085/empmgmt/test/employees/ise?delay=500";
}else{
url = "http://localhost:8085/empmgmt/test/employees?delay=2000";
}
log.info("Count value {}", count.get());
long startTime = System.nanoTime();
ResponseEntity<String> response =
restTemplate.getForEntity(url, String.class);
count.incrementAndGet();
long endTime = System.nanoTime();
long durationMillis = (endTime - startTime) / 1_000_000;
log.info("Downstream call took: {} ms", durationMillis);
return response;
}
public ResponseEntity<String> getEmployeeFallback(Throwable t) {
log.error("Fallback triggered due to exception: {} Count is {} ", t.getMessage(), count.get());
count.incrementAndGet();
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body("Service unavailable");
}
}
Logs in Circuit Breaker
Count value 1
Fallback triggered due to exception: 500 Server Error on GET request for "http://localhost:8085/empmgmt/test/employees/ise": "{ "message": "500 Internal Server Error"}" Count is 1
Count value 2
Fallback triggered due to exception: 500 Server Error on GET request for "http://localhost:8085/empmgmt/test/employees/ise": "{ "message": "500 Internal Server Error"}" Count is 2
Count value 3
Fallback triggered due to exception: 500 Server Error on GET request for "http://localhost:8085/empmgmt/test/employees/ise": "{ "message": "500 Internal Server Error"}" Count is 3
Count value 4
Fallback triggered due to exception: 500 Server Error on GET request for "http://localhost:8085/empmgmt/test/employees/ise": "{ "message": "500 Internal Server Error"}" Count is 4
Count value 5
Fallback triggered due to exception: 500 Server Error on GET request for "http://localhost:8085/empmgmt/test/employees/ise": "{ "message": "500 Internal Server Error"}" Count is 5
Count value 6
Fallback triggered due to exception: 500 Server Error on GET request for "http://localhost:8085/empmgmt/test/employees/ise": "{ "message": "500 Internal Server Error"}" Count is 6
Count value 7
Fallback triggered due to exception: 500 Server Error on GET request for "http://localhost:8085/empmgmt/test/employees/ise": "{ "message": "500 Internal Server Error"}" Count is 7
Count value 8
⚠️ CircuitBreaker [EmployeeService] moved to OPEN state at 2026-08-16T14:16:53.120914500+05:30[Asia/Calcutta]
Fallback triggered due to exception: 500 Server Error on GET request for "http://localhost:8085/empmgmt/test/employees/ise": "{ "message": "500 Internal Server Error"}" Count is 8
Fallback triggered due to exception: CircuitBreaker 'EmployeeService' is OPEN and does not permit further calls Count is 9
Fallback triggered due to exception: CircuitBreaker 'EmployeeService' is OPEN and does not permit further calls Count is 10
Fallback triggered due to exception: CircuitBreaker 'EmployeeService' is OPEN and does not permit further calls Count is 11
Fallback triggered due to exception: CircuitBreaker 'EmployeeService' is OPEN and does not permit further calls Count is 12
CircuitBreaker [EmployeeService] transitioned: State transition from OPEN to HALF_OPEN
Count value 13
Downstream call took: 2009 ms
Count value 14
Downstream call took: 2006 ms
Count value 15
Downstream call took: 2008 ms
CircuitBreaker [EmployeeService] transitioned: State transition from HALF_OPEN to CLOSED
Count value 16
Downstream call took: 2012 ms
Count value 17
Downstream call took: 2008 ms
Count value 18
Downstream call took: 2016 ms
Count value 19
Downstream call took: 2013 ms
Count value 20
Downstream call took: 2013 ms






