1. Imagine You’re a Dog Living in a House π
Hey, buddy! Imagine you’re a dog living in a big house.
Every day, your owner gives you biscuits.
One morning, you feel hungry and bark at your owner.
Your owner doesn’t hear you.
You bark again.
Nothing happens!
You bark ten more times, but your owner is still busy.
Now you’re exhausted, and your owner is getting annoyed by all the barking.
Wouldn’t it be better to stop barking for a while and try again later?
Of course!
You decide to wait for a little while. After some time, you bark once again to check whether your owner is available.
This time, your owner hears you and gives you a biscuit.
Congratulations! πΆ
You have just understood the basic idea behind the Circuit Breaker Pattern.
In software engineering, a Circuit Breaker helps prevent an application from repeatedly calling a downstream service that is likely to fail.
Instead of continuously making unsuccessful calls, the application temporarily stops sending requests to that service and allows it time to recover.
Let’s understand how this works in Spring Boot microservices.
2. What Is the Circuit Breaker Pattern?
Imagine we have two microservices:
- Order Service: Receives customer requests.
- Product Service: Provides product information.
When a customer requests an order, Order Service calls Product Service.
Client
|
v
Order Service
|
v
Product Service
|
v
Product Details
Everything works perfectly when Product Service is healthy.
But what happens when Product Service goes down?
Order Service continues calling it.
Every request fails, consumes resources, and may wait for a timeout before returning an error.
If thousands of requests arrive, Order Service can waste a significant amount of time and resources calling a service that is unavailable.
Repeated calls can also increase the load on Product Service and make recovery more difficult.
We need a smarter solution.
The Circuit Breaker Pattern prevents repeated calls to a downstream service when failures indicate that the service may be unhealthy.
Think of it like an electrical circuit breaker in your house.
When an electrical problem occurs, the breaker interrupts the circuit to protect the system.
Similarly, a software Circuit Breaker can temporarily stop calls to an unhealthy downstream service.
3. Why Do We Need Circuit Breaker in Microservices?
Let’s understand the problem with a simple example.
Imagine Product Service has stopped working.
Order Service receives five requests.
Without a Circuit Breaker, the application may make five unsuccessful calls.
Order Service
|
+---- Call 1 ----> Product Service β
|
+---- Call 2 ----> Product Service β
|
+---- Call 3 ----> Product Service β
|
+---- Call 4 ----> Product Service β
|
+---- Call 5 ----> Product Service β
What are the problems?
- Order Service wastes resources on unsuccessful calls.
- Requests may experience unnecessary latency.
- Threads can remain occupied while waiting for timeouts.
- Product Service receives additional traffic while it is struggling to recover.
Now imagine we introduce a Circuit Breaker.
After enough failures are detected, the Circuit Breaker opens.
Order Service
|
v
Circuit Breaker
|
X Circuit is OPEN
|
v
Calls rejected immediately
Instead of making another downstream call, the application can fail fast or return an appropriate fallback response.
After a configured waiting period, the Circuit Breaker can allow a few test calls to check whether Product Service has recovered.
This is the core idea behind Circuit Breaker.
4. The Three States of Circuit Breaker
Resilience4j Circuit Breaker has three main states:
- CLOSED
- OPEN
- HALF_OPEN
Imagine these states as three different rules your owner gives you about asking for biscuits.
4.1 CLOSED State
Your owner is available and giving you biscuits.
You are allowed to bark whenever you need one.
This is the normal operating state.
In a Circuit Breaker, CLOSED means calls are allowed to reach the downstream service.
The Circuit Breaker monitors the results of those calls.
If the configured failure threshold is exceeded, it transitions to OPEN.
Order Service
|
v
Circuit Breaker
CLOSED
|
v
Product Service
Remember: CLOSED does not mean the service is closed or unavailable. It means the circuit allows calls.
4.2 OPEN State
Now imagine your owner is sleeping.
You keep barking, but nobody responds.
Finally, you decide to stop barking for a while.
This is similar to the OPEN state.
When the Circuit Breaker opens, calls to the protected downstream operation are blocked or rejected without making the normal downstream call.
Order Service
|
v
Circuit Breaker
OPEN
|
X
|
No normal call to Product Service
The application can use a fallback response if one is configured.
For example:
“Product Service is temporarily unavailable. Please try again later.”
The Circuit Breaker remains OPEN for the configured waiting period.
This protects the downstream service from repeated calls and prevents the caller from wasting resources on calls that are likely to fail.
4.3 HALF_OPEN State
After sleeping for some time, you wake up.
You want to check whether your owner is available again.
Instead of barking continuously, you bark a few times and observe what happens.
This is similar to the HALF_OPEN state.
After the waiting period, the Circuit Breaker allows a limited number of test calls to the downstream service.
If those calls succeed according to the configured criteria, the circuit can return to CLOSED.
If the failure criteria are still met, it can return to OPEN.
CLOSED
|
Failure threshold exceeded
|
v
OPEN
|
Wait duration expires
|
v
HALF_OPEN
/ \
Successful Failures
tests |
| |
v v
CLOSED OPEN
The HALF_OPEN state helps determine whether the downstream service has recovered without immediately allowing unrestricted traffic.
5. CLOSED vs. OPEN vs. HALF_OPEN
Let’s compare the three states.
| Feature | CLOSED | OPEN | HALF_OPEN |
|---|---|---|---|
| Calls allowed? | Yes | Normal downstream calls are blocked | Limited test calls |
| Main purpose | Normal operation and monitoring | Protect against repeated failures | Check whether the service recovered |
| What happens after success? | Continues normally | Waits for the configured transition | Can return to CLOSED |
| What happens after failures? | May transition to OPEN | Remains OPEN | Can transition back to OPEN |
The easiest way to remember the states is:
- CLOSED: Everything is normal.
- OPEN: Stop calling the unhealthy service.
- HALF_OPEN: Allow a few test calls to check recovery.
Now let’s implement this using Spring Boot and Resilience4j.
6. Implement Circuit Breaker in Spring Boot
Imagine we have an Order Service that calls Product Service using a Feign Client.
We want to configure a Circuit Breaker that opens when the observed failure rate reaches a threshold.
Step 1: Add the Resilience4j dependency
Add this dependency to your Maven pom.xml:
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
<version>2.1.0</version>
</dependency>
Use a version compatible with your Spring Boot application.
Step 2: Create the Product Feign Client
@FeignClient(name = "product-service")
public interface ProductClient {
@GetMapping("/products/{id}")
String getProductById(
@PathVariable("id") String id
);
}
This interface allows Order Service to communicate with Product Service through a Feign Client.
The service name product-service can be resolved through the service-discovery and load-balancing setup in your application.
Step 3: Create the Order Service
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final ProductClient productClient;
public OrderService(ProductClient productClient) {
this.productClient = productClient;
}
@CircuitBreaker(
name = "productService",
fallbackMethod = "productFallback"
)
public String invokeProductAPI(String id) {
return productClient.getProductById(id);
}
public String productFallback(
String id,
Throwable ex) {
return "Product Service is temporarily unavailable.";
}
}
Let’s understand the important parts.
@CircuitBreaker
This annotation enables Circuit Breaker protection for the method when Resilience4j’s Spring integration is configured.
name = "productService"
This identifies the Circuit Breaker instance whose configuration we will define in application.properties.
fallbackMethod = "productFallback"
This specifies the fallback method to use when the protected call fails and the exception is handled by the fallback mechanism.
Important: The fallback signature should match the original method’s arguments, followed optionally by a compatible exception parameter. Since invokeProductAPI accepts a String id, the fallback accepts the same ID followed by Throwable.
The fallback can handle failures from the protected call, including failures that occur while the circuit is OPEN. It is not a separate retry mechanism.
Step 4: Create the Order Controller
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/{id}")
public String getOrder(@PathVariable String id) {
return orderService.invokeProductAPI(id);
}
}
Now you can test the endpoint:
GET http://localhost:8081/orders/101
When Product Service is healthy and the circuit is CLOSED, the request can return product information.
When the circuit is OPEN, the protected call is rejected, and the configured fallback can return the alternative response.
7. Configure Circuit Breaker in application.properties
Now comes the important part: configuration.
Add the following properties to your application.properties file.
server.port=8081
spring.application.name=order-service
# Circuit Breaker configuration
resilience4j.circuitbreaker.instances.productService.sliding-window-type=COUNT_BASED
resilience4j.circuitbreaker.instances.productService.sliding-window-size=10
resilience4j.circuitbreaker.instances.productService.minimum-number-of-calls=5
resilience4j.circuitbreaker.instances.productService.failure-rate-threshold=50
resilience4j.circuitbreaker.instances.productService.wait-duration-in-open-state=10s
resilience4j.circuitbreaker.instances.productService.permitted-number-of-calls-in-half-open-state=3
resilience4j.circuitbreaker.instances.productService.automatic-transition-from-open-to-half-open-enabled=true
Let’s understand each property.
7.1 sliding-window-type
resilience4j.circuitbreaker.instances.productService.sliding-window-type=COUNT_BASED
Resilience4j supports two main sliding-window types.
COUNT_BASED
Tracks a configured number of recent calls.
TIME_BASED
Tracks calls within a configured time window.
For example:
resilience4j.circuitbreaker.instances.productService.sliding-window-type=COUNT_BASED
resilience4j.circuitbreaker.instances.productService.sliding-window-size=10
This configuration uses the last 10 recorded calls in the sliding window.
Alternatively:
resilience4j.circuitbreaker.instances.productService.sliding-window-type=TIME_BASED
resilience4j.circuitbreaker.instances.productService.sliding-window-size=10
This configures a time-based window of 10 seconds.
The exact behavior depends on the selected window type and its associated configuration.
7.2 sliding-window-size
resilience4j.circuitbreaker.instances.productService.sliding-window-size=10
This specifies the window size.
- For
COUNT_BASED, it represents the number of calls. - For
TIME_BASED, it represents the number of seconds in the window.
The Circuit Breaker uses the recorded results in that window to calculate the failure rate.
7.3 minimum-number-of-calls
resilience4j.circuitbreaker.instances.productService.minimum-number-of-calls=5
This defines the minimum number of recorded calls required before the Circuit Breaker evaluates the failure-rate threshold.
For example, if the minimum is five, the Circuit Breaker does not evaluate the failure rate until at least five calls have been recorded in the relevant window.
This helps prevent the Circuit Breaker from opening based on an extremely small sample of requests.
7.4 failure-rate-threshold
resilience4j.circuitbreaker.instances.productService.failure-rate-threshold=50
This specifies the failure-rate threshold as a percentage.
In this example, the threshold is 50%.
Suppose the relevant window contains 10 recorded calls:
- 5 calls failed.
- 5 calls succeeded.
The failure rate is 50%.
Once the minimum-call requirement is satisfied, reaching or exceeding the configured failure-rate threshold can cause the circuit to transition to OPEN.
The Circuit Breaker does not open merely because one request fails. It evaluates the recorded outcomes and configured thresholds.
7.5 wait-duration-in-open-state
resilience4j.circuitbreaker.instances.productService.wait-duration-in-open-state=10s
This defines how long the Circuit Breaker remains OPEN before it can transition to HALF_OPEN.
In this example, the waiting period is 10 seconds.
During that period, normal calls to the protected downstream operation are rejected by the Circuit Breaker.
7.6 permitted-number-of-calls-in-half-open-state
resilience4j.circuitbreaker.instances.productService.permitted-number-of-calls-in-half-open-state=3
This defines how many test calls are permitted while the Circuit Breaker is HALF_OPEN.
In this example, three calls are permitted.
Their recorded outcomes help determine whether the circuit should return to CLOSED or transition back to OPEN.
7.7 automatic-transition-from-open-to-half-open-enabled
resilience4j.circuitbreaker.instances.productService.automatic-transition-from-open-to-half-open-enabled=true
This enables automatic transition from OPEN to HALF_OPEN after the configured waiting period.
If automatic transition is disabled, the state transition is triggered when a later call checks the Circuit Breaker’s state after the waiting period.
The automatic option is useful when you want the state to change without waiting for another incoming request.
8. Let’s Test the Circuit Breaker
Imagine Product Service is running normally.
The Circuit Breaker starts in CLOSED.
Now, we deliberately stop Product Service to simulate an outage.
Order Service continues receiving requests.
Let’s walk through what can happen with our example configuration.
Step 1: Send requests while Product Service is unavailable
The downstream calls fail.
Resilience4j records the relevant outcomes in the sliding window.
Step 2: Reach the minimum number of calls
Our configuration requires at least five recorded calls before evaluating the failure rate.
Until the minimum is met, the Circuit Breaker does not open based on the failure-rate threshold alone.
Step 3: Reach the failure threshold
Suppose the window contains enough failed calls to reach or exceed the 50% failure-rate threshold.
The Circuit Breaker transitions from CLOSED to OPEN.
Step 4: Calls are rejected while OPEN
During the 10-second open-state waiting period, normal calls to Product Service are blocked by the Circuit Breaker.
The application can return the configured fallback response.
Step 5: Transition to HALF_OPEN
After the waiting period, the Circuit Breaker automatically transitions to HALF_OPEN because automatic transition is enabled.
Step 6: Allow test calls
The Circuit Breaker permits up to three test calls according to the configuration.
If the recorded results satisfy the success criteria, it can transition to CLOSED.
If the configured failure criteria are met, it can return to OPEN.
This is how Circuit Breaker helps protect your microservice from repeatedly calling a dependency that is failing.
9. What Happens If We Want to Record Specific Exceptions?
By default, Resilience4j Circuit Breaker records exceptions according to its configured failure rules. Its default behavior includes runtime exceptions and errors, but it is important to understand which exceptions your application actually produces.
For example, a validation exception should not necessarily count as a downstream service failure.
You can configure exceptions that should be recorded or ignored.
Example:
resilience4j.circuitbreaker.instances.productService.record-exceptions=java.io.IOException,org.springframework.web.client.HttpServerErrorException
resilience4j.circuitbreaker.instances.productService.ignore-exceptions=java.lang.IllegalArgumentException
Let’s understand this.
record-exceptions
Specifies exception types that should be recorded as failures.
ignore-exceptions
Specifies exception types that should be ignored by the Circuit Breaker when calculating the failure rate.
For example, if IllegalArgumentException represents invalid input, you may not want that failure to affect the health assessment of Product Service.
Configure exception handling carefully for the client library and version used by your application. Feign exceptions and network failures may require different handling depending on how they are represented.
10. How Does Resilience4j Change the Circuit Breaker State?
You might be wondering:
“Who actually changes the state from CLOSED to OPEN?”
Or:
“How does the Circuit Breaker automatically move from OPEN to HALF_OPEN after 10 seconds?”
Resilience4j contains the internal logic to manage these state transitions.
The Spring integration uses an aspect to intercept calls to methods annotated with @CircuitBreaker.
The Circuit Breaker then evaluates recorded call outcomes and the configured thresholds.
Conceptually, the process looks like this:
Method called
|
v
Circuit Breaker intercepts call
|
v
Check current state
|
+---- CLOSED ----> Allow downstream call
|
+---- OPEN ------> Reject protected call
|
+---- HALF_OPEN -> Allow limited test calls
When the configured failure threshold is exceeded in CLOSED, the Circuit Breaker can transition to OPEN.
When the OPEN waiting period expires, it can transition to HALF_OPEN.
When the test-call outcomes are evaluated, the circuit can return to CLOSED or move back to OPEN.
The framework manages these transitions, so you do not have to manually maintain the three states in your service code.
How does automatic transition work?
Your source PDF explains that Resilience4j can use a ScheduledExecutorService to schedule a transition from OPEN to HALF_OPEN.
Conceptually, a scheduled task is created with a delay.
scheduler.schedule(
this::toHalfOpenState,
waitDurationInMillis,
TimeUnit.MILLISECONDS
);
This illustrates the idea of scheduling the state transition. It is a simplified example, not the complete internal implementation.
The actual scheduling and state-management logic is handled by the Resilience4j framework.
11. Circuit Breaker vs. Retry
You have already learned about Retry in the previous article.
Now let’s compare Retry and Circuit Breaker.
Imagine you bark for biscuits, but your owner doesn’t hear you.
Retry: You try again after a suitable delay because the failure might be temporary.
Circuit Breaker: If repeated failures suggest that your owner is unavailable, you stop barking for a while and check again later.
In microservices, they solve different problems.
| Feature | Retry | Circuit Breaker |
|---|---|---|
| Main purpose | Try a failed operation again | Stop repeated calls to an unhealthy dependency |
| Behavior after failure | May make another attempt | Records outcomes and may open the circuit |
| Delay | Can be fixed or calculated using a backoff strategy | Uses an open-state waiting period |
| Protects against | Some temporary failures | Repeated calls to failing dependencies |
| State management | Tracks retry attempts | Uses CLOSED, OPEN, and HALF_OPEN states |
Both patterns can be used together, but their interaction needs careful configuration.
For example, aggressive retries can generate additional failures and affect the Circuit Breaker’s recorded results.
A well-designed application should limit retry attempts and ensure that the Circuit Breaker protects the appropriate downstream operation.
12. Real-World Example: An E-Commerce Application
Imagine an e-commerce application with three microservices:
- Product Service
- Payment Service
- Inventory Service
The Order Service calls all three services during the order process.
Now suppose Payment Service becomes unavailable.
Without appropriate resilience mechanisms, Order Service may continue making unsuccessful calls to Payment Service, increasing latency and consuming resources.
With a Circuit Breaker, the Payment operation can be protected separately.
Order Service
|
+------------+------------+
| | |
v v v
Product API Payment API Inventory API
| | |
v v v
Product CB Payment CB Inventory CB
| | |
v v v
Product Payment Inventory
Service Service Service
Each Circuit Breaker can monitor the results of calls to its own downstream dependency.
If Payment Service fails repeatedly, its Circuit Breaker can open without automatically opening the Circuit Breakers for Product Service or Inventory Service.
This is an important advantage of isolating resilience policies by dependency.
The application still needs appropriate business handling. For example, if payment is mandatory to complete an order, a fallback should not silently pretend that payment succeeded.
13. Best Practices for Circuit Breaker
Before using Circuit Breaker in production, remember these points.
1. Choose the correct failure threshold
A threshold that is too sensitive can open the circuit unnecessarily. A threshold that is too high can allow excessive failures before the circuit opens.
2. Configure the sliding window carefully
Choose between COUNT_BASED and TIME_BASED according to your traffic patterns and monitoring requirements.
3. Set a suitable minimum number of calls
Avoid making decisions based on an extremely small number of requests.
4. Choose an appropriate open-state duration
The waiting period should give the downstream service an opportunity to recover without creating unnecessary delays.
5. Use sensible fallback behavior
Return an appropriate error or alternative response. Never report a business operation as successful if it actually failed.
6. Configure timeouts
Circuit Breaker does not replace connection timeouts or read timeouts. A slow downstream call can still occupy resources until its timeout expires.
7. Monitor the Circuit Breaker
Monitor failure rates, state transitions, rejected calls, and downstream response times.
8. Use Retry carefully
Retry and Circuit Breaker can complement each other, but retries should be bounded and limited to appropriate failures.
14. Key Takeaways πΆ
Let’s finish our dog lesson!
Imagine you’re a dog waiting for biscuits.
At first, your owner is available, so you can bark normally.
When your owner becomes unavailable, you stop barking for a while.
After some time, you try a few times to check whether your owner is available again.
If everything works, you return to normal.
That’s the basic idea behind Circuit Breaker.
Remember these five things:
- CLOSED: Calls are allowed, and outcomes are monitored.
- OPEN: Calls to the protected downstream operation are rejected.
- HALF_OPEN: A limited number of test calls check whether the service has recovered.
- Resilience4j: Provides Circuit Breaker support for Spring Boot applications.
- Configuration matters: Sliding windows, minimum calls, failure thresholds, and open-state waiting periods determine how the circuit behaves.
The easiest way to remember it:
Retry means trying again.
Circuit Breaker means knowing when to stop trying for a while.
A smart dog doesn’t bark forever at an owner who is sleeping. Similarly, a resilient microservice doesn’t need to keep calling a dependency that is likely to fail.
In the next article, we’ll explore TimeLimiter in Spring Boot Microservices, using the same simple dog-friendly approach.
Until then, keep coding and keep learning! πΆπ»