MicroservicesResiliencebeginner
Updated:

Circuit Breaker Pattern

7 min read

Learn how the circuit breaker pattern protects microservices from cascading failures, its three states, and how to implement it with Resilience4j in Spring Boot.

TL;DR – Quick Answer

The circuit breaker pattern stops an application from repeatedly calling a service that is failing. Like an electrical breaker, it 'trips' after a threshold of failures and fails fast instead of waiting on timeouts, then periodically tests whether the service has recovered. This prevents one slow or dead service from cascading into failures across everything that depends on it.

On This Page

The circuit breaker pattern borrows its name from the breaker in your home's electrical panel. When too much current flows, the breaker trips and cuts the circuit so the wiring does not catch fire. In microservices, the "fire" is a cascading failure, and the breaker cuts off calls to a service that has stopped responding correctly.

This is one of the most important patterns in distributed systems because of a hard truth: the moment you turn an in-process method call into a network call, that call can hang. And a hung call is more dangerous than a fast error, because it ties up the caller's resources while it waits.

Why cascading failure happens

Suppose your Order service calls your Payment service over HTTP. Payment gets slow — its database is struggling — and each call now takes 30 seconds instead of 50 milliseconds.

Order keeps accepting requests, and each one spawns a call to Payment that blocks a thread for 30 seconds. Within a minute, every thread in Order's pool is stuck waiting on Payment. Now Order stops responding too, so the services that call Order start hanging. One slow service has taken down half the system. This chain reaction is cascading failure, and it is exactly what the core microservices model exposes you to.

Pro tip: The danger is not the failing service — it is the healthy callers waiting on it. A circuit breaker protects the caller, not the callee. It gives the struggling service room to recover by taking the load off it.

The three states

A circuit breaker is a small state machine wrapped around a remote call:

  • Closed — normal operation. Calls pass through, and the breaker counts failures. If the failure rate crosses a threshold (say 50% of the last 20 calls), it trips to open.
  • Open — the breaker is tripped. Calls fail immediately, without touching the service. This "fail fast" is the whole point: no threads hang. After a wait period, it moves to half-open.
  • Half-open — a probe. The breaker lets a few trial calls through. If they succeed, the service has recovered and the breaker closes. If they fail, it snaps back to open and waits again.

The transition from open back to half-open is what makes the pattern self-healing. You never have to manually restart anything; the breaker keeps testing and closes on its own.

Implementing it with Resilience4j

In Spring Boot the standard library is Resilience4j. You configure the breaker in application.yml:

resilience4j:
  circuitbreaker:
    instances:
      paymentService:
        sliding-window-size: 20              # look at the last 20 calls
        failure-rate-threshold: 50           # trip if 50% fail
        wait-duration-in-open-state: 10s     # stay open 10s before probing
        permitted-number-of-calls-in-half-open-state: 3
        slow-call-duration-threshold: 2s     # a 2s+ call counts as a failure
        slow-call-rate-threshold: 50

Then you wrap the risky call and supply a fallback:

@Service
public class PaymentClient {

    private final RestClient rest;

    public PaymentClient(RestClient.Builder builder) {
        this.rest = builder.baseUrl("http://payment-service").build();
    }

    @CircuitBreaker(name = "paymentService", fallbackMethod = "queueForLater")
    public PaymentResult charge(PaymentRequest request) {
        return rest.post()
                .uri("/payments")
                .body(request)
                .retrieve()
                .body(PaymentResult.class);
    }

    // Called when the breaker is open or the call fails
    private PaymentResult queueForLater(PaymentRequest request, Throwable cause) {
        // Degrade gracefully: accept the order, settle payment asynchronously
        outbox.save(request);
        return PaymentResult.pending(request.orderId());
    }
}

When Payment is healthy, charge runs normally. When it starts failing, the breaker trips and every subsequent call jumps straight to queueForLater without waiting on a dead service. Instead of an error, the customer's order is accepted and settled later — a degraded but working experience.

The fallback is where the design happens

A circuit breaker without a thoughtful fallback just turns slow failures into fast failures. That is better, but the real win is a fallback that keeps the feature usable:

  • Return cached or last-known data for a read (show yesterday's prices).
  • Queue the work and process it when the service recovers, as above.
  • Return a sensible default (an empty recommendations list, not a crash).
  • Show a clear "temporarily unavailable" message for things that genuinely cannot degrade.

Choosing the right fallback is a business decision, not a technical one. "Can we accept the order now and charge later?" is a question for the product owner, not just the developer.

How it fits with timeouts and retries

A common interview trap is confusing these three. They work together:

  • A timeout caps how long one call can hang. Without it, the breaker has nothing to measure and calls can block forever.
  • A retry re-attempts a call that failed for a transient reason, like a brief network blip.
  • The circuit breaker cuts off calls entirely once failures are frequent, so you stop hammering a service that is genuinely down.

The dangerous combination is retries without a breaker: when a service is overloaded, retries triple the traffic and finish it off. Wrap retries in a breaker so the retries stop once the failure pattern is clear.

Common mistake: Setting the failure threshold so high or the window so large that the breaker never trips in time. If it takes 500 failures to open, cascading failure has already spread. Tune the window and threshold against real traffic, and watch the breaker's state transitions in your monitoring.

The bulkhead: contain the damage further

The circuit breaker is usually paired with a second pattern called the bulkhead, named after the watertight compartments in a ship's hull. If one compartment floods, the bulkheads stop the water from sinking the whole vessel.

Applied to software, a bulkhead isolates resources so that trouble in one dependency cannot drain the resources every other dependency needs. Concretely, you give calls to the Payment service their own bounded thread pool or connection pool, separate from calls to the Catalog service. If Payment slows down and saturates its pool, Catalog calls keep flowing through their own untouched pool. Without bulkheads, a single slow dependency can exhaust one shared pool and starve everything.

Resilience4j supports this directly. The mental model is simple: the circuit breaker decides whether to call, and the bulkhead limits how many concurrent calls a dependency can consume. Together they stop both the "keep calling a dead service" failure and the "one hog starves everyone" failure.

Tuning against real traffic

The hardest part of a circuit breaker is not the code — it is the numbers. A window that is too small trips on noise; too large and it reacts too late. A wait-in-open duration that is too short hammers a recovering service before it is ready; too long and you stay degraded after recovery.

There are no universal values. Start from your actual traffic: how many calls per second does this dependency get, what is its normal latency, and how long does it typically take to recover from a blip? Set the slow-call threshold just above normal latency, size the window to cover a few seconds of real traffic, and then watch the breaker in production and adjust. Treat these as living settings, not constants you set once and forget.

You cannot tune what you cannot see

A circuit breaker is only as good as your visibility into it. You need to know how often it trips, for which service, and whether fallbacks are firing. That is why resilience and observability are studied together — breaker state changes should be metrics on a dashboard and alerts, not surprises you discover from angry users. Pair this with clean REST exception handling so the errors that reach clients are deliberate, not accidental stack traces.

Interview relevance

Expect "how do you prevent one slow service from taking down the whole system?" The winning answer walks through cascading failure, names the closed-open-half-open states, and stresses that the breaker protects the caller and needs a real fallback. If you can add that a breaker without a timeout is meaningless and that retries without a breaker make overload worse, you sound like someone who has run this in production, not just read about it.

Not every call needs a breaker

A final piece of honest guidance: do not wrap every method in a circuit breaker reflexively. The pattern only makes sense around calls that cross a process boundary — a network call to another service, a database, or an external API. Wrapping a plain in-memory computation in a breaker adds overhead and configuration for a failure mode that cannot happen. Reserve breakers for remote dependencies that can genuinely be slow or unavailable, and leave local, fast, deterministic code alone. Applied where it belongs, the breaker is one of the highest-leverage tools you have; sprayed everywhere, it is just noise.

The circuit breaker is the entry point to resilience engineering. From here, look at where it lives in the request path via the API gateway, and study the saga pattern for how to keep data consistent when a call in the middle of a workflow fails and your fallback kicks in.

Frequently Asked Questions

What problem does the circuit breaker pattern solve?
It solves cascading failure. When a downstream service is slow, callers pile up waiting on timeouts, exhaust their threads, and start failing themselves, spreading the outage upstream. A circuit breaker detects the failures, trips, and fails fast so callers stay healthy instead of hanging on a dead dependency.
What are the three states of a circuit breaker?
Closed means calls pass through normally while failures are counted. Open means the breaker has tripped and calls fail immediately without hitting the service. Half-open means the breaker lets a few trial calls through to check if the service recovered, closing again on success or reopening on failure.
What is the difference between a circuit breaker and a retry?
A retry attempts the same failed call again, hoping it was a transient blip. A circuit breaker does the opposite when failures persist: it stops calling entirely so you do not hammer a struggling service. They are complementary — retry transient errors, but let the breaker cut off a service that is genuinely down.
What is a fallback in the circuit breaker pattern?
A fallback is the alternative response you return when the breaker is open and the real call cannot be made. It might be cached data, a default value, or a friendly 'try again later' message. A good fallback keeps the user experience degraded but functional instead of showing an error.
Does a circuit breaker replace timeouts?
No, it complements them. You still need a timeout so a single call does not hang forever, and the breaker uses those timeouts and errors as the failures it counts. Timeouts protect individual calls; the breaker protects the caller from making more calls once the pattern of failure is clear.

Want to Build Your Career in Java Full Stack with AI?

Join CodeBegun and train with working industry engineers — Explore the Program

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 15 July 2026 LinkedIn
Chat with us