Overview
Learn how to make your microservice calls resilient using the Circuit Breaker pattern with Resilience4j and Spring Boot — complete demo, step-by-step commands, class-by-class explanations, sample outputs, real-world use cases, and production tips.
Why This Matters
In distributed systems, a failing downstream service can cascade and cause overall system outages. The Circuit Breaker pattern:
- Detects a failing dependency,
- Stops sending requests to it (to avoid wasting resources),
- Returns a graceful fallback response,
- Periodically tests the dependency and restores normal operation when healthy.
This reduces downtime, protects thread pools, keeps user experience reasonable, and prevents retry storms.
Demo Summary (What You Have)
A two-service Maven demo:
-
hello-service (port 8081) — simple REST provider that intentionally fails intermittently.
End point
GET /api/hello
-
client-service (port 8080) — calls hello-service using RestTemplate and is protected by Resilience4j @CircuitBreaker with a fallback.
End point
GET /api/get-message
Run both (hello-service and client-service)
Then test
GET http://localhost:8080/api/get-message
Architecture Diagram
This is a small, focused flow suitable for drawing a diagram
Files & Code: Class-By-Class Explanation
hello-service
HelloServiceApplication.java
-
Standard @SpringBootApplication bootstrap class.
HelloController.java
@RestController
public class HelloController {
private static int counter = 0;
@GetMapping("/api/hello")
public String sayHello() {
counter++;
// simulate intermittent failure: fail on every 3rd request
if (counter % 3 == 0) {
throw new RuntimeException("Simulated failure from Hello-Service!");
}
return "Hello from Hello-Service! (count=" + counter + ")";
}
}
Explanation: This controller intentionally throws a RuntimeException on periodic calls to simulate transient failures you’d see in real systems (DB outage, bad data, timeouts).
client-service
ClientServiceApplication.java
- Standard Spring Boot main class. No special config required.
AppConfig.java
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Explanation: Provides a single RestTemplate bean. Ensure RestTemplate is a Spring bean so AOP/resilience proxies can work properly.
HelloClientService.java
@Service
public class HelloClientService {
private final RestTemplate restTemplate;
@Value("${hello.service.url}")
private String helloServiceUrl;
public HelloClientService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@CircuitBreaker(name = "helloService", fallbackMethod = "fallbackHello")
public String getHelloMessage() {
System.out.println("Calling hello service: " + helloServiceUrl);
return restTemplate.getForObject(helloServiceUrl, String.class);
}
public String fallbackHello(Throwable t) {
System.out.println("Fallback triggered: " + t);
return "Hello Service is currently unavailable. Please try again later.";
}
}
Explanation (crucial bits)
-
@CircuitBreaker(name = “helloService”, fallbackMethod = “fallbackHello”) wraps the getHelloMessage() call in a circuit breaker. The name links to configuration properties.
-
fallbackHello(Throwable t) is called when the call fails according to the breaker rules.
The fallback must:
o Be in the same class
o Have the same return type
o Accept the original method parameters (none here) and a final Throwable parameter (or Exception/Throwable compatible with thrown exceptions)
-
Important: The method must be public, and the class must be a Spring bean (@Service), so proxy-based AOP works.
ClientController.java
@RestController
public class ClientController {
private final HelloClientService helloClientService;
public ClientController(HelloClientService helloClientService) {
this.helloClientService = helloClientService;
}
@GetMapping("/api/get-message")
public String getMessage() {
return helloClientService.getHelloMessage();
}
}
Explanation: Simple controller delegating to HelloClientService. This ensures the call goes through the proxy where the circuit breaker is applied.
Configuration Used (client-service application.properties)
Key configuration used in the demo
server.port=8080
spring.application.name=client-service
hello.service.url=http://localhost:8081/api/hello
resilience4j.circuitbreaker.instances.helloService.registerHealthIndicator=true
resilience4j.circuitbreaker.instances.helloService.slidingWindowSize=5
resilience4j.circuitbreaker.instances.helloService.minimumNumberOfCalls=2
resilience4j.circuitbreaker.instances.helloService.failureRateThreshold=50
resilience4j.circuitbreaker.instances.helloService.waitDurationInOpenState=10s
logging.level.io.github.resilience4j.circuitbreaker=DEBUG
Meaning of important properties
- slidingWindowSize: number of calls the breaker monitors for failure percentage.
- minimumNumberOfCalls: minimum calls before failure rate is evaluated.
- failureRateThreshold: percent failures (e.g., 50) to open the circuit.
- waitDurationInOpenState: how long the circuit stays open before moving to half-open.
- registerHealthIndicator: exposes breaker state via actuator.
Step-By-Step Run & Expected Outputs
Start Services
- Start hello-service
Visit: http://localhost:8081/api/hello
Returns → “Hello from Hello-Service!” (or throws simulated failure)
- Start client-service
Visit: http://localhost:8080/api/get-message
Test Scenarios & Outputs
Scenario A — Hello-Service Healthy
Make a request
GET http://localhost:8080/api/get-message
Client logs
Calling hello service: http://localhost:8081/api/hello
2025-11-13T11:58:23.366+05:30 DEBUG 32692 --- [client-service] [nio-8080-exec-8] i.g.r.c.i.CircuitBreakerStateMachine : CircuitBreaker 'helloService' succeeded:
2025-11-13T11:58:23.366+05:30 DEBUG 32692 --- [client-service] [nio-8080-exec-8] i.g.r.c.i.CircuitBreakerStateMachine : Event SUCCESS published: 2025-11-13T11:58:23.366634+05:30[Asia/Calcutta]: CircuitBreaker 'helloService' recorded a successful call. Elapsed time: 15 ms
Response
Hello from Hello-Service! (count=4)
Scenario B — Hello-Service Intermittent Failures
If you call repeatedly and hello-service throws RuntimeException on some requests:
● Successful calls: client returns the hello message.
● When a downstream call returns HTTP 500/exception:
o Resilience4j records the failure.
o If failure rate exceeds threshold (e.g., 50% over sliding window), the Circuit becomes **OPEN**.
o While OPEN, calls are short-circuited; **fallbackHello**() is immediately executed — no network call. n **Client response while fallback active**
Client Response while active
Response
Hello Service is currently unavailable. Please try again later.
Sample client log sequence
Calling hello service: http://localhost:8081/api/hello
2025-11-13T12:00:55.842+05:30 DEBUG 32692 --- [client-service] [nio-8080-exec-1] i.g.r.c.i.CircuitBreakerStateMachine : CircuitBreaker 'helloService' recorded an exception as failure:
Scenario C — Recovery
After waitDurationInOpenState (10s):
- Circuit goes to HALF_OPEN: a few test calls are allowed.
- If test calls succeed, breaker CLOSES and normal traffic resumes.
- If tests fail, the breaker goes back to OPEN.
Real-World Use Cases
- Payment Integration – A checkout service calling an external bank API: if the bank’s API becomes slow/fails, the circuit breaker returns a user-friendly message and prevents retry storms.
- Third-party rate-limited APIs – APIs with limited calls per second — when limits are reached, the circuit opens to avoid hitting quotas further; fallback returns cached data.
- Microservice chains inside an enterprise – Service A calls B, which calls C. If C is unstable, open breakers protect B and A, preventing system-wide slowdown.
- Feature toggles and graceful degradation – When a non-critical feature service fails, return a simpler result to keep core functionality available (e.g., return product list without recommendations).
Advantages & Business Value
- Fault isolation — prevents one bad dependency from cascading.
- Faster failure response — fails fast instead of waiting on timeouts.
- Graceful degradation — offers fallback responses instead of full outages.
- Resource protection — avoids wasting CPU, threads, and network.
- Auto recovery — automatically returns to normal when the dependency is healthy.
- Observability — breaker states and metrics can be exposed via the Actuator and monitored.
Sample logs you’ll see (realistic)
Calling hello service: http://localhost:8081/api/hello
2025-11-13T12:00:55.842+05:30 DEBUG 32692 --- [client-service] [nio-8080-exec-1] i.g.r.c.i.CircuitBreakerStateMachine : CircuitBreaker 'helloService' recorded an exception as failure
2025-11-13T12:00:55.847+05:30 DEBUG 32692 --- [client-service] [nio-8080-exec-1] i.g.r.c.i.CircuitBreakerStateMachine : Event ERROR published: 2025-11-13T12:00:55.847908200+05:30[Asia/Calcutta]: CircuitBreaker 'helloService' recorded an error: 'org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 : "{"timestamp":"2025-11-13T06:30:55.842+00:00","status":500,"error":"Internal Server Error","path":"/api/hello"}"'. Elapsed time: 8 ms
Fallback triggered: org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 : "{"timestamp":"2025-11-13T06:30:55.842+00:00","status":500,"error":"Internal Server Error","path":"/api/hello"}"
