<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Backend Engineering - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Backend Engineering - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 09 Jun 2026 23:07:40 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/backend-engineering/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build Your Own Circuit Breaker in Spring Boot – and Really Understand Resilience4j ]]>
                </title>
                <description>
                    <![CDATA[ This article explains how to design and implement your own circuit breaker in Spring Boot using explicit failure tracking, a scheduler-driven recovery model, and clear state transitions. Instead of relying solely on Resilience4j, we’ll walk through t... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-your-own-circuit-breaker-in-spring-boot-and-really-understand-resilience4j/</link>
                <guid isPermaLink="false">69938789dce780a9836b8f09</guid>
                
                    <category>
                        <![CDATA[ Springboot ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Resilience ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Java ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Circuit breaker pattern ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Microservices ]]>
                    </category>
                
                    <category>
                        <![CDATA[ fault tolerance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Backend Engineering ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jessica Patel ]]>
                </dc:creator>
                <pubDate>Mon, 16 Feb 2026 21:09:29 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1771276149217/e55b0a5c-53e2-4d2c-a004-1467a7c2b17a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>This article explains how to design and implement your own circuit breaker in Spring Boot using explicit failure tracking, a scheduler-driven recovery model, and clear state transitions.</p>
<p>Instead of relying solely on Resilience4j, we’ll walk through the internal mechanics so you understand how circuit breakers actually work.</p>
<h2 id="heading-what-well-cover">What We’ll Cover</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-prerequisites-and-technical-context">Prerequisites and Technical Context</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-a-circuit-breaker-in-distributed-systems">What Is a Circuit Breaker in Distributed Systems</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-why-circuit-breakers-matter-in-spring-boot">Why Circuit Breakers Matter in Spring Boot</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-circuit-breakers-are-foundational">Why Circuit Breakers Are Foundational</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-problem-circuit-breakers-solve-that-times-and-retries-do-not">What Problem Circuit Breakers Solve That Timeouts and Retries Do Not</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-circuit-breaker-state-model">The Circuit Breaker State Model</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-not-just-use-resilience4j">Why Not Just Use Resilience4j?</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-design-goals-for-a-custom-circuit-breaker">Design Goals for a Custom Circuit Breaker</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-a-minimal-working-circuitbreaker-class">How to Build a Minimal Working CircuitBreaker Class</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-concurrency-and-state-transition-guarantees">Concurrency and State Transition Guarantees</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-explaining-the-state-model-in-the-class">Explaining the State Model in the Class</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-failure-tracking-inside-the-class">Failure Tracking Inside the Class</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-closed-state-transitions-to-open">How Closed State Transitions to Open</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-open-state-behavior-in-the-class">OPEN State Behavior in the Class</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-schedulerdriven-recovery-entering-halfopen">Scheduler‑Driven Recovery: Entering HALF_OPEN</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-spring-boot-scheduler-example">Spring Boot Scheduler Example</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-this-connects-to-execution-flow">How This Connects to Execution Flow</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-scheduler-design-and-thread-safety">Scheduler Design and Thread Safety</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-we-avoid-scheduled-for-this-design">Why We Avoid @Scheduled for This Design</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-bringing-it-all-together">Bringing It All Together</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-observability-making-the-breaker-understandable">Observability: Making the Breaker Understandable</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-handling-different-failure-types">Handling Different Failure Types</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-custom-breaker-vs-resilience4j">Custom Breaker vs Resilience4j</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-you-should-not-build-your-own">When You Should Not Build Your Own</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-extending-the-design">Extending the Design</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-common-mistakes">Common Mistakes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-prerequisites-and-technical-context"><strong>Prerequisites and Technical Context</strong></h2>
<p>This article assumes you are comfortable with core Spring Boot and Java concepts. We won’t cover framework fundamentals or basic concurrency principles in depth. Here’s what you’ll need to know:</p>
<h3 id="heading-spring-boot-basics">Spring Boot Basics</h3>
<p>You should be comfortable with how dependency injection works in Spring, how to define <code>@Configuration</code> classes and <code>@Bean</code> definitions, and the basic service-layer structure of a Spring application. In this tutorial, we’ll treat the circuit breaker as a plain Java component and wire it into Spring through configuration classes rather than annotations.</p>
<h3 id="heading-java-concurrency-fundamentals">Java Concurrency Fundamentals</h3>
<p>You don’t need to be a concurrency expert, but you should be comfortable with Java’s basic concurrency tools. The implementation uses <code>AtomicInteger</code>, volatile fields, a <code>ScheduledExecutorService</code>, and simple synchronization, so you should understand why shared mutable state is dangerous, how atomic operations differ from synchronized blocks, and why state transitions in a shared state machine must be serialized.</p>
<h3 id="heading-functional-interfaces">Functional Interfaces</h3>
<p>The circuit breaker exposes an <code>execute(Supplier&lt;T&gt;)</code> method, so you should be comfortable using <code>Supplier&lt;T&gt;</code>, writing simple lambda expressions, and wrapping outbound service calls inside a function you can pass to the breaker.</p>
<h3 id="heading-resilience4j-basics">Resilience4j Basics</h3>
<p>You don’t need hands-on Resilience4j experience, but you should know that it’s a lightweight Java fault-tolerance library that offers circuit breakers, retries, rate limiters, bulkheads, and is commonly used in Spring Boot via annotations or config. In this article we’ll only reference Resilience4j for comparison, not for actual configuration or usage.</p>
<h2 id="heading-what-is-a-circuit-breaker-in-distributed-systems">What Is a Circuit Breaker in Distributed Systems?</h2>
<p>A circuit breaker is a fault-tolerance pattern that stops a system from repeatedly attempting operations that are likely to fail.</p>
<p>The name comes from electrical engineering. In a physical circuit, a breaker “opens” when the current becomes unsafe, preventing damage. After a cooldown period, it allows current to flow again to test whether the issue has been resolved.</p>
<p>In software, the same principle applies. When Service A depends on Service B, and Service B becomes slow or unavailable, naïvely retrying every request can:</p>
<ul>
<li><p>Exhaust thread pools</p>
</li>
<li><p>Saturate connection pools</p>
</li>
<li><p>Increase latency across the system</p>
</li>
<li><p>Trigger cascading failures</p>
</li>
<li><p>Bring down otherwise healthy services</p>
</li>
</ul>
<p>Instead of continuing to send requests to a failing dependency, a circuit breaker:</p>
<ul>
<li><p>Detects repeated failures</p>
</li>
<li><p>Opens the circuit and blocks calls</p>
</li>
<li><p>Fails fast without attempting the operation</p>
</li>
<li><p>Periodically tests whether the dependency has recovered</p>
</li>
</ul>
<p>This turns uncontrolled failure into controlled degradation.</p>
<h3 id="heading-why-circuit-breakers-matter-in-spring-boot">Why Circuit Breakers Matter in Spring Boot</h3>
<p>Because circuit breakers are a foundational resilience pattern in distributed systems, most Spring Boot teams reach immediately for Resilience4j or legacy Hystrix‑style abstractions – and for good reason. These libraries are mature, well-tested, and production-proven.​</p>
<p>However, treating circuit breakers as black boxes often leads to:​</p>
<ul>
<li><p>Misconfigured thresholds</p>
</li>
<li><p>Incorrect assumptions about failure handling</p>
</li>
<li><p>Difficulty extending behavior beyond library defaults</p>
</li>
<li><p>Debugging issues where “the breaker opened, but we don’t know why”​</p>
</li>
</ul>
<p>Building your own circuit breaker – even if you never ship it to production – forces you to understand the mechanics that actually protect your system. In some cases, a custom implementation also provides flexibility that general-purpose libraries cannot.</p>
<h3 id="heading-why-circuit-breakers-are-foundational">Why Circuit Breakers Are Foundational</h3>
<p>Circuit breakers are a foundational resilience pattern because they protect your scarcest resources (like threads, network and database connections, and CPU time) from being exhausted by a failing dependency.</p>
<p>Without a breaker, a single slow service can gradually consume all of those resources and turn a local problem into a system-wide outage.</p>
<p>Circuit breakers enforce isolation boundaries between services and sit alongside timeouts, retries, bulkheads, and rate limiters, but they make one crucial strategic choice that simple retries do not: they <strong>stop trying for now</strong>. That decision is what prevents cascading collapse.</p>
<h3 id="heading-what-problem-circuit-breakers-solve-that-timeouts-and-retries-do-not">What Problem Circuit Breakers Solve That Timeouts and Retries Do Not</h3>
<p>Timeouts and retries are reactive: timeouts cap how long you wait, and retries try the same operation again in the hope it succeeds.</p>
<p>A circuit breaker is proactive. It monitors failure patterns and, once a threshold is crossed, temporarily disables the failing integration point so new requests are rejected immediately instead of timing out.This dramatically reduces resource waste and stabilizes the system under stress.</p>
<h3 id="heading-the-circuit-breaker-state-model">The Circuit Breaker State Model</h3>
<p>Any circuit breaker – library-based or custom – follows the same conceptual state machine.​</p>
<ol>
<li><p><strong>Closed:</strong> In the Closed state, all requests are allowed and failures are simply monitored.</p>
</li>
<li><p><strong>Open:</strong> When failures cross a configured threshold, the breaker moves to Open, blocks new requests, and makes them fail immediately.</p>
</li>
<li><p><strong>Half-Open:</strong> After a cooldown period, it enters Half-Open, where it lets a small number of trial requests through to test whether the dependency has recovered; based on those results, it either returns to Closed or goes back to Open.</p>
</li>
</ol>
<p>The complexity lies not in the states themselves, but in <strong>how and when transitions occur</strong>.​</p>
<h3 id="heading-why-not-just-use-resilience4j">Why Not Just Use Resilience4j?</h3>
<p>Resilience4j is excellent, but there are valid reasons to build your own:​</p>
<ul>
<li><p>You want non-standard failure logic (for example, domain-aware errors).</p>
</li>
<li><p>You need custom recovery strategies.</p>
</li>
<li><p>You want state persisted or shared differently.</p>
</li>
<li><p>You need tight integration with business metrics.</p>
</li>
<li><p>You want to understand the internals for tuning and debugging.​</p>
</li>
</ul>
<p>More importantly, understanding the internals prevents misuse. Many production incidents stem from misconfigured circuit breakers rather than missing ones.​</p>
<h2 id="heading-design-goals-for-a-custom-circuit-breaker">Design Goals for a Custom Circuit Breaker</h2>
<p>Before writing any code, we need to be clear about what “correct” behavior looks like. A circuit breaker seems simple in theory, but subtle design mistakes can introduce race conditions, false openings, or silent failures where it stops protecting the system.</p>
<p>The following goals shape a predictable and production-safe implementation.</p>
<h3 id="heading-thread-safe-and-low-overhead">Thread-Safe and Low Overhead</h3>
<p>The breaker sits on the hot path of outbound calls, so every protected request passes through it. If it introduces lock contention or heavy synchronization, it quickly becomes a bottleneck.</p>
<p>The implementation needs to avoid coarse-grained locking, use atomic primitives carefully, and serialize state transitions without blocking execution more than necessary. Thread safety is non‑negotiable: a circuit breaker that misbehaves under concurrency is worse than having no breaker at all.</p>
<h3 id="heading-predictable-state-transitions">Predictable State Transitions</h3>
<p>Circuit breakers are state machines. If their transitions are inconsistent or prone to races, you end up with split‑brain behavior – one thread believes the breaker is OPEN while another believes it is CLOSED – and your protection becomes undefined.</p>
<p>To avoid this, every transition (CLOSED → OPEN → HALF_OPEN → CLOSED) must be explicit, atomic, and deterministic, all guarded by a single transition mechanism. In this design, predictability matters far more than cleverness.</p>
<h3 id="heading-explicit-failure-tracking">Explicit Failure Tracking</h3>
<p>Not every failure should open the breaker. If you blindly count every exception, you risk opening the breaker on client validation errors, treating business rule violations as infrastructure failures, and hiding real domain bugs behind resilience logic.</p>
<p>Failure classification has to be deliberate: the breaker should react only to infrastructure‑level problems such as timeouts, connection errors, and 5xx responses, not to domain logic errors. Keeping that separation ensures your resilience layer stays aligned with actual failure modes.</p>
<h3 id="heading-time-based-recovery-using-a-scheduler">Time-Based Recovery Using a Scheduler</h3>
<p>Some implementations check timestamps on every request to decide when to move from OPEN to HALF_OPEN, adding extra branching to the hot path.</p>
<p>Instead, this design uses a scheduler: when the breaker opens, it schedules a recovery attempt, keeps the OPEN state purely fail‑fast, and avoids request‑driven polling. That approach reduces branching and contention under load. Recovery should be controlled and predictable – not opportunistic.</p>
<h3 id="heading-framework-agnostic-core-logic">Framework-Agnostic Core Logic</h3>
<p>The breaker itself should be plain Java – no Spring annotations, no AOP, and no direct framework coupling. That choice makes unit testing easier, keeps the component portable, and preserves a clean separation of concerns with less hidden magic. Spring should wrap the breaker, not define it, so your resilience strategy is not trapped inside any one framework’s abstractions.</p>
<h3 id="heading-easy-integration-into-spring-boot">Easy Integration into Spring Boot</h3>
<p>Although the core logic is framework‑agnostic, it still needs to plug cleanly into a Spring application. That means wiring it via <code>@Configuration</code>, supporting dependency injection, and calling it from clear execution points in your service layer. Resilience behavior should be obvious in code reviews. Hiding it behind annotations often leads to confusion when you are debugging production issues.</p>
<h2 id="heading-how-to-build-a-minimal-working-circuitbreaker-class">How to Build a Minimal Working CircuitBreaker Class</h2>
<p>Now let’s turn the conceptual components into a single cohesive class. This is still a minimal implementation, but it’s complete enough to demonstrate state, failure tracking, scheduling, and execution logic in one place.</p>
<p>A minimal circuit breaker consists of:​</p>
<ol>
<li><p>State holder</p>
</li>
<li><p>Failure tracker</p>
</li>
<li><p>Transition rules</p>
</li>
<li><p>Scheduler for recovery</p>
</li>
<li><p>Execution guard</p>
</li>
</ol>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CircuitBreaker</span> </span>{

    <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">State</span> </span>{
        CLOSED,
        OPEN,
        HALF_OPEN
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> ScheduledExecutorService scheduler;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> failureThreshold;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> halfOpenTrialLimit;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Duration openCooldown;

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> AtomicInteger failureCount = <span class="hljs-keyword">new</span> AtomicInteger(<span class="hljs-number">0</span>);
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> AtomicInteger halfOpenTrials = <span class="hljs-keyword">new</span> AtomicInteger(<span class="hljs-number">0</span>);

    <span class="hljs-comment">// All transitions go through this field, guarded by `synchronized` blocks.</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">volatile</span> State state = State.CLOSED;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">CircuitBreaker</span><span class="hljs-params">(
            ScheduledExecutorService scheduler,
            <span class="hljs-keyword">int</span> failureThreshold,
            <span class="hljs-keyword">int</span> halfOpenTrialLimit,
            Duration openCooldown
    )</span> </span>{
        <span class="hljs-keyword">this</span>.scheduler = scheduler;
        <span class="hljs-keyword">this</span>.failureThreshold = failureThreshold;
        <span class="hljs-keyword">this</span>.halfOpenTrialLimit = halfOpenTrialLimit;
        <span class="hljs-keyword">this</span>.openCooldown = openCooldown;
    }

    <span class="hljs-keyword">public</span> &lt;T&gt; <span class="hljs-function">T <span class="hljs-title">execute</span><span class="hljs-params">(Supplier&lt;T&gt; action)</span> </span>{
        <span class="hljs-comment">// 1. Guards the functionality based on its current state. </span>
        <span class="hljs-comment">//We are using synchronized block for thread safety. </span>
        <span class="hljs-comment">// Make sure another thread does not override our current state</span>
        State current;
        <span class="hljs-keyword">synchronized</span> (<span class="hljs-keyword">this</span>) {
            current = state;

            <span class="hljs-keyword">if</span> (current == State.OPEN) {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(<span class="hljs-string">"Circuit breaker is OPEN. Call rejected."</span>);
            }

            <span class="hljs-keyword">if</span> (current == State.HALF_OPEN) {
                <span class="hljs-keyword">int</span> trials = halfOpenTrials.incrementAndGet();
                <span class="hljs-keyword">if</span> (trials &gt; halfOpenTrialLimit) {
                    <span class="hljs-comment">// Too many trial requests; fail fast.</span>
                    halfOpenTrials.decrementAndGet();
                    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(<span class="hljs-string">"Circuit breaker is HALF_OPEN. Trial limit exceeded."</span>);
                }
            }
        }

        <span class="hljs-comment">// 2. Execute the business functionality here. For e.g API calls to other systems </span>
        <span class="hljs-keyword">try</span> {
            T result = action.get();
            <span class="hljs-comment">// 3. Record success</span>
            onSuccess();
            <span class="hljs-keyword">return</span> result;
        } <span class="hljs-keyword">catch</span> (Throwable t) {
            <span class="hljs-comment">// 3. Record failure</span>
            onFailure(t);
            <span class="hljs-comment">// 4. Propagate to caller</span>
            <span class="hljs-keyword">if</span> (t <span class="hljs-keyword">instanceof</span> RuntimeException re) {
                <span class="hljs-keyword">throw</span> re;
            }
            <span class="hljs-keyword">if</span> (t <span class="hljs-keyword">instanceof</span> Error e) {
                <span class="hljs-keyword">throw</span> e;
            }
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(t);
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onSuccess</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">synchronized</span> (<span class="hljs-keyword">this</span>) {
            failureCount.set(<span class="hljs-number">0</span>);

            <span class="hljs-keyword">if</span> (state == State.HALF_OPEN) {
                <span class="hljs-comment">// A successful trial closes the breaker.</span>
                transitionToClosed();
            }
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onFailure</span><span class="hljs-params">(Throwable t)</span> </span>{
        <span class="hljs-comment">// Example: only count "server-side" failures.</span>
        <span class="hljs-keyword">boolean</span> breakerRelevant = <span class="hljs-keyword">true</span>; <span class="hljs-comment">// placeholder for domain-specific checks</span>

        <span class="hljs-keyword">if</span> (!breakerRelevant) {
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-keyword">synchronized</span> (<span class="hljs-keyword">this</span>) {
            <span class="hljs-keyword">int</span> failures = failureCount.incrementAndGet();
            <span class="hljs-keyword">if</span> (state == State.CLOSED &amp;&amp; failures &gt;= failureThreshold) {
                transitionToOpen();
            } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (state == State.HALF_OPEN) {
                <span class="hljs-comment">// Any failure in HALF_OPEN sends us back to OPEN.</span>
                transitionToOpen();
            }
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">transitionToOpen</span><span class="hljs-params">()</span> </span>{
        state = State.OPEN;
        <span class="hljs-comment">// Reset counters so the next CLOSED phase starts clean.</span>
        failureCount.set(<span class="hljs-number">0</span>);
        halfOpenTrials.set(<span class="hljs-number">0</span>);
        scheduleHalfOpen();
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">transitionToHalfOpen</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">synchronized</span> (<span class="hljs-keyword">this</span>) {
            state = State.HALF_OPEN;
            halfOpenTrials.set(<span class="hljs-number">0</span>);
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">transitionToClosed</span><span class="hljs-params">()</span> </span>{
        state = State.CLOSED;
        failureCount.set(<span class="hljs-number">0</span>);
        halfOpenTrials.set(<span class="hljs-number">0</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">scheduleHalfOpen</span><span class="hljs-params">()</span> </span>{
        scheduler.schedule(
                <span class="hljs-keyword">this</span>::transitionToHalfOpen,
                openCooldown.toMillis(),
                TimeUnit.MILLISECONDS
        );
    }
}
</code></pre>
<p>Now we’ll walk through each responsibility in that class: why the fields exist, how state transitions work, where concurrency guarantees matter, how execution is guarded, and how the scheduler drives recovery.</p>
<p>Each subsection maps directly back to part of this class – we’re not introducing new concepts, just explaining the behavior implemented within the code above.</p>
<h3 id="heading-concurrency-and-state-transition-guarantees">Concurrency and State Transition Guarantees</h3>
<p>Although the breaker uses atomic primitives for counters and a volatile state field, this only works because <strong>all state transitions are guarded consistently</strong>.​</p>
<p>In practice, every transition – CLOSED → OPEN, OPEN → HALF_OPEN, HALF_OPEN → CLOSED – must be performed under the same synchronization mechanism as shown below: either a single lock or a CAS-based state machine. Mixing unsynchronized state writes with atomic counters can lead to split-brain behavior (for example, one thread reopening the breaker while another closes it).​</p>
<pre><code class="lang-java"><span class="hljs-keyword">synchronized</span> (<span class="hljs-keyword">this</span>) {
            current = state;

            <span class="hljs-keyword">if</span> (current == State.OPEN) {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(<span class="hljs-string">"Circuit breaker is OPEN. Call rejected."</span>);
            }

            <span class="hljs-keyword">if</span> (current == State.HALF_OPEN) {
                <span class="hljs-keyword">int</span> trials = halfOpenTrials.incrementAndGet();
                <span class="hljs-keyword">if</span> (trials &gt; halfOpenTrialLimit) {
                    <span class="hljs-comment">// Too many trial requests; fail fast.</span>
                    halfOpenTrials.decrementAndGet();
                    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(<span class="hljs-string">"Circuit breaker is HALF_OPEN. Trial limit exceeded."</span>);
                }
            }
        }
</code></pre>
<p>The rule is simple: <strong>reads may be optimistic, but writes and transitions must be serialized</strong>.</p>
<h3 id="heading-explaining-the-state-model-in-the-class">Explaining the State Model in the Class</h3>
<p>At the core of the implementation is a simple but strict state machine represented by the State enum: CLOSED, OPEN and HALF_OPEN</p>
<p>The <code>state</code> field is declared <code>volatile</code> so changes are immediately visible across threads. When one thread moves the breaker to a new state, other threads see that update without delay.</p>
<p>Alongside the state, the class maintains <code>failureCount</code> and <code>halfOpenTrials</code> counters using <code>AtomicInteger</code> (<strong>Refer to the code in the above section</strong>). These track how failures accumulate and how many recovery attempts we have made, without resorting to coarse‑grained locks.</p>
<p>The key design idea is separation of responsibilities: the <code>enum</code> captures the current mode of operation, while the atomic counters hold the metrics that influence state transitions. Atomic increments alone do not guarantee safe transitions, though, so all updates to the state still follow a consistent serialization strategy to avoid race conditions.</p>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">State</span> </span>{
        CLOSED,
        OPEN,
        HALF_OPEN
    }
</code></pre>
<p>This structure gives us a clear foundation: a small, explicit state machine with observable transition boundaries.</p>
<h3 id="heading-failure-tracking-inside-the-class">Failure Tracking Inside the Class</h3>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onFailure</span><span class="hljs-params">(Throwable t)</span> </span>{
        <span class="hljs-comment">// Example: only count "server-side" failures.</span>
        <span class="hljs-keyword">boolean</span> breakerRelevant = <span class="hljs-keyword">true</span>; <span class="hljs-comment">// placeholder for domain-specific checks</span>

        <span class="hljs-keyword">if</span> (!breakerRelevant) {
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-keyword">synchronized</span> (<span class="hljs-keyword">this</span>) {
            <span class="hljs-keyword">int</span> failures = failureCount.incrementAndGet();
            <span class="hljs-keyword">if</span> (state == State.CLOSED &amp;&amp; failures &gt;= failureThreshold) {
                transitionToOpen();
            } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (state == State.HALF_OPEN) {
                <span class="hljs-comment">// Any failure in HALF_OPEN sends us back to OPEN.</span>
                transitionToOpen();
            }
        }
    }
</code></pre>
<p>In this implementation, failure tracking is intentionally simple: we count <strong>consecutive</strong> failures. Each time a protected call throws an exception we classify as breaker‑relevant, <code>failureCount</code> is incremented. On a successful call, the counter resets.</p>
<p>I chose consecutive failures for clarity rather than sophistication. More advanced strategies, like sliding time windows or failure ratios, introduce extra state and timing complexity. When you’re learning how a breaker works, a simple counter makes the transition rules easy to reason about and easy to test.</p>
<p>Equally important, the breaker should not treat every exception the same. Domain validation errors, client misuse, and business rule violations shouldn’t affect the breaker’s state. Only infrastructure‑level problems (like timeouts, connection failures, or 5xx responses) should move the breaker toward OPEN. That separation keeps the breaker focused on dependency instability, not application bugs or bad inputs.</p>
<h3 id="heading-how-closed-state-transitions-to-open">How Closed State Transitions to Open</h3>
<p>When the breaker is in the CLOSED state, all requests flow through normally. In this phase the breaker is purely observational: it monitors outcomes and increments <code>failureCount</code> whenever a breaker‑relevant exception occurs.</p>
<p>Inside the <code>onFailure</code> method (shown in the above section), once the <code>failureCount</code> exceeds the configured threshold, the breaker transitions to OPEN. This transition must be atomic and serialized – otherwise, multiple threads could try to open the breaker at the same time, leading to inconsistent scheduling or duplicate recovery tasks.</p>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">transitionToOpen</span><span class="hljs-params">()</span> </span>{
        state = State.OPEN;
        <span class="hljs-comment">// Reset counters so the next CLOSED phase starts clean.</span>
        failureCount.set(<span class="hljs-number">0</span>);
        halfOpenTrials.set(<span class="hljs-number">0</span>);
        scheduleHalfOpen();
    }
</code></pre>
<p>Moving to OPEN immediately changes system behavior. From that point on, new requests are rejected without attempting the protected operation, which shields downstream services and preserves local resources such as threads and connection pools.</p>
<h3 id="heading-open-state-behavior-in-the-class">OPEN State Behavior in the Class</h3>
<p>The OPEN state represents pure fail‑fast behavior. While the breaker is open, no protected calls are executed. The <code>execute()</code> method immediately throws an exception indicating that the circuit is open.</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> &lt;T&gt; <span class="hljs-function">T <span class="hljs-title">execute</span><span class="hljs-params">(Supplier&lt;T&gt; action)</span> </span>{
        <span class="hljs-comment">// 1. Guards the functionality based on its current state. </span>
        <span class="hljs-comment">//We are using synchronized block for thread safety. </span>
        <span class="hljs-comment">// Make sure another thread does not override our current state</span>
        State current;
        <span class="hljs-keyword">synchronized</span> (<span class="hljs-keyword">this</span>) {
            current = state;

            <span class="hljs-keyword">if</span> (current == State.OPEN) {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(<span class="hljs-string">"Circuit breaker is OPEN. Call rejected."</span>);
            }
....
}
</code></pre>
<p>This behavior is not about improving latency – it is about resource protection. Letting calls continue and simply “wait for timeouts” would still tie up threads and connections. The value of the OPEN state is that it refuses to participate in propagating failure at all.</p>
<p>In this state, the breaker has a single responsibility: wait for the scheduled recovery attempt. It doesn’t check timestamps on each request or poll in the hot path. Its behavior is deterministic: reject immediately and let the scheduler decide when to try again.</p>
<h3 id="heading-schedulerdriven-recovery-entering-halfopen">Scheduler‑Driven Recovery: Entering HALF_OPEN</h3>
<p>When the breaker transitions to OPEN, it immediately schedules a delayed task using the injected ScheduledExecutorService. After the configured cooldown period elapses, that task transitions the breaker to HALF_OPEN.</p>
<pre><code class="lang-java"><span class="hljs-comment">// Refer below methods from the main code </span>

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">transitionToOpen</span><span class="hljs-params">()</span> </span>{
        state = State.OPEN;
        <span class="hljs-comment">// Reset counters so the next CLOSED phase starts clean.</span>
        failureCount.set(<span class="hljs-number">0</span>);
        halfOpenTrials.set(<span class="hljs-number">0</span>);
        scheduleHalfOpen(); <span class="hljs-comment">// schedule a delayed task after changing the state to State.Open</span>
    }

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">scheduleHalfOpen</span><span class="hljs-params">()</span> </span>{
        scheduler.schedule(
                <span class="hljs-keyword">this</span>::transitionToHalfOpen,
                openCooldown.toMillis(),
                TimeUnit.MILLISECONDS
        );
    }
</code></pre>
<p>This design keeps time-based logic out of the request execution path. Rather than checking elapsed time on every call, the breaker delegates recovery timing to a dedicated scheduler thread. This reduces conditional logic under load and keeps the execute() method focused on guarding execution.</p>
<p>The scheduler must be reliable and isolated. A single-threaded executor is typically sufficient because transitions are rare and lightweight. More importantly, transitions should be idempotent so that unexpected rescheduling does not corrupt state.</p>
<h2 id="heading-spring-boot-scheduler-example">Spring Boot Scheduler Example</h2>
<p>In Spring Boot, you can wire a dedicated <code>ScheduledExecutorService</code> bean to drive state transitions instead of using plain Java threads.</p>
<pre><code class="lang-java"><span class="hljs-meta">@Configuration</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CircuitBreakerConfig</span> </span>{

    <span class="hljs-comment">// First bean </span>
    <span class="hljs-meta">@Bean</span>
    <span class="hljs-function">ScheduledExecutorService <span class="hljs-title">circuitBreakerScheduler</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> Executors.newSingleThreadScheduledExecutor();
    }

    <span class="hljs-comment">// Second bean </span>
    <span class="hljs-meta">@Bean</span>
    <span class="hljs-function">CircuitBreaker <span class="hljs-title">circuitBreaker</span><span class="hljs-params">(ScheduledExecutorService circuitBreakerScheduler)</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> CircuitBreaker(
                circuitBreakerScheduler,
                <span class="hljs-number">5</span>,                     <span class="hljs-comment">// failureThreshold</span>
                <span class="hljs-number">2</span>,                     <span class="hljs-comment">// halfOpenTrialLimit</span>
                Duration.ofSeconds(<span class="hljs-number">30</span>) <span class="hljs-comment">// openCooldown</span>
        );
    }
}
</code></pre>
<p>The configuration class above wires the circuit breaker into the Spring container without introducing framework coupling into the breaker itself.</p>
<p>The first bean <code>circuitBreakerScheduler()</code> defines a dedicated <code>ScheduledExecutorService</code>. This executor is responsible exclusively for time-based state transitions. When the breaker moves to OPEN, it uses this scheduler to queue a delayed task that transitions the state to HALF_OPEN.</p>
<p>Using a single-threaded executor is intentional. Circuit breaker transitions are lightweight and infrequent, so parallel scheduling is unnecessary. A single thread guarantees serialized transition execution and avoids overlapping recovery attempts.</p>
<p>The second bean constructs the <code>CircuitBreaker</code> itself. Here we inject the scheduler and configure three things: a failure threshold of 5 consecutive errors, a half‑open trial limit of 2 test requests, and a 30‑second cooldown before we attempt recovery again. This configuration makes the breaker’s behavior explicit and easy to reason about – there are no hidden properties files or annotations, because everything that affects resilience is defined in one place.</p>
<p>At this point, the breaker is a fully managed Spring bean that you can inject into services and use programmatically.</p>
<h3 id="heading-how-this-connects-to-execution-flow">How This Connects to Execution Flow</h3>
<p>Once registered as a bean, the breaker becomes part of the application’s dependency graph. A typical service might inject it and wrap outbound calls:</p>
<pre><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExternalApiService</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> CircuitBreaker circuitBreaker;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> RestTemplate restTemplate;

    ExternalApiService(CircuitBreaker circuitBreaker, RestTemplate restTemplate) {
        <span class="hljs-keyword">this</span>.circuitBreaker = circuitBreaker;
        <span class="hljs-keyword">this</span>.restTemplate = restTemplate;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">callExternal</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> circuitBreaker.execute(() -&gt;
                restTemplate.getForObject(<span class="hljs-string">"http://external/api"</span>, String.class)
        );
    }
}
</code></pre>
<p>Every outbound call to the external system flows through the breaker’s <code>execute()</code> method, which enforces the current state rules before allowing the call to proceed. That makes resilience behavior explicit at the integration boundary: anyone reviewing the service can immediately see that the call is protected. There is no hidden interception layer and no AOP proxy quietly changing behavior at runtime.</p>
<h3 id="heading-scheduler-design-and-thread-safety">Scheduler Design and Thread Safety</h3>
<p>The scheduler’s only responsibility is delayed state transition. It doesn’t execute business logic and it doesn’t evaluate request outcomes. Its purpose is narrowly scoped: move the breaker from OPEN to HALF_OPEN after a cooldown.</p>
<p>Because the executor is single-threaded, scheduled tasks cannot overlap. But this doesn’t eliminate concurrency concerns entirely. Request threads may still attempt transitions at the same time the scheduler fires. For this reason, transition methods such as <code>transitionToHalfOpen()</code> and <code>transitionToOpen()</code> must remain serialized and idempotent.</p>
<p>In other words, even though the scheduler simplifies time-based recovery, it doesn’t replace the need for careful state management.</p>
<p>The architectural separation looks like this:</p>
<ul>
<li><p>Request threads → enforce execution rules and record outcomes</p>
</li>
<li><p>Scheduler thread → handle time-based recovery transitions</p>
</li>
</ul>
<p>Keeping these responsibilities separate reduces complexity in the hot path and improves predictability under load.</p>
<h3 id="heading-why-we-avoid-scheduled-for-this-design">Why We Avoid @Scheduled for This Design</h3>
<p>Spring provides @Scheduled as an alternative mechanism for time-based tasks. While convenient, it introduces global scheduling behavior and reduces isolation.</p>
<p>By using a dedicated <code>ScheduledExecutorService</code> for the breaker, we avoid interference with other scheduled jobs, keep lifecycle control explicit, and tie scheduling logic directly to breaker transitions.</p>
<p>This design reinforces the principle that resilience components should be isolated and predictable.</p>
<h3 id="heading-bringing-it-all-together">Bringing It All Together</h3>
<p>At this stage, the full interaction looks like this:</p>
<ol>
<li><p>A service wraps its dependency call with <code>circuitBreaker.execute()</code>.</p>
</li>
<li><p>If the breaker is CLOSED, the call proceeds and any relevant failures are counted.</p>
</li>
<li><p>When failures exceed the threshold, the breaker moves to OPEN and schedules a recovery attempt.</p>
</li>
<li><p>While OPEN, calls fail immediately without hitting the downstream system.</p>
</li>
<li><p>After the cooldown period, the scheduler transitions the breaker to HALF_OPEN.</p>
</li>
<li><p>A small number of trial calls then decide whether the breaker returns to CLOSED or goes back to OPEN.</p>
</li>
</ol>
<p>Nothing is hidden: every transition is visible in code, every configuration value is explicit, and each thread involved has a single responsibility. That clarity is what makes a custom implementation useful for learning – and safe when it is designed correctly.</p>
<h3 id="heading-observability-making-the-breaker-understandable">Observability: Making the Breaker Understandable</h3>
<p>A circuit breaker without observability is risky. At a minimum you should expose the current state, the failure count, the time of the last transition, and how long the breaker has been open.</p>
<p>On the metrics side, track how often the breaker opens, how many calls are rejected per second, and the success rate of recovery attempts.</p>
<p>Your logs should record state transitions at INFO level and failure classification decisions at DEBUG. With that level of visibility, your custom breaker is often easier to understand and tune than what many libraries provide out of the box..</p>
<h3 id="heading-handling-different-failure-types">Handling Different Failure Types</h3>
<p>Not all failures are equal.</p>
<ul>
<li><p>API Response Timeouts → breaker‑relevant</p>
</li>
<li><p>API 5xx responses → breaker‑relevant</p>
</li>
<li><p>API 4xx responses → usually not</p>
</li>
<li><p>Any data or business validation errors → never</p>
</li>
</ul>
<p>A custom breaker lets you apply this kind of <strong>business‑aware classification</strong>, which is often hard to express cleanly with generic libraries.</p>
<h2 id="heading-custom-breaker-vs-resilience4j">Custom Breaker vs Resilience4j</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Aspect</strong></td><td><strong>Custom Breaker</strong></td><td><strong>Resilience4j</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Learning value</td><td>High</td><td>Low</td></tr>
<tr>
<td>Flexibility</td><td>High</td><td>Medium</td></tr>
<tr>
<td>Time to implement</td><td>Medium</td><td>Low</td></tr>
<tr>
<td>Operational maturity</td><td>Depends</td><td>High</td></tr>
<tr>
<td>Custom failure logic</td><td>Easy</td><td>Limited</td></tr>
<tr>
<td>Tooling / metrics</td><td>You wire metrics, logs, observability manually</td><td>Built-in metrics, logging, and integrations</td></tr>
</tbody>
</table>
</div><p>The choice is not binary. Many teams prototype with a custom breaker and later replace it with Resilience4j – now correctly configured.​</p>
<h2 id="heading-when-you-should-not-build-your-own">When You Should Not Build Your Own</h2>
<p>Do not build a custom breaker if:​</p>
<ul>
<li><p>You lack observability.</p>
</li>
<li><p>You do not understand concurrency.</p>
</li>
<li><p>You need advanced features immediately.</p>
</li>
<li><p>Your system is safety-critical.​</p>
</li>
</ul>
<p>For example, if you are building a <strong>payments platform</strong> with strict SLAs and cannot afford to battle-test a custom breaker, stick with a mature library like Resilience4j. The risk of subtle concurrency bugs, misclassified failures, or scheduler misconfigurations is too high to experiment in production.​</p>
<h2 id="heading-extending-the-design">Extending the Design</h2>
<p>Once you understand the core, you can add:​</p>
<ul>
<li><p>Sliding window metrics.</p>
</li>
<li><p>Adaptive thresholds.</p>
</li>
<li><p>Persistent breaker state.</p>
</li>
<li><p>Distributed breakers (per dependency).</p>
</li>
<li><p>Integration with feature flags.​</p>
</li>
</ul>
<p>These extensions are much easier when you control the internals.​</p>
<h2 id="heading-common-mistakes">Common Mistakes</h2>
<p>Common mistakes when working with circuit breakers include:​</p>
<ul>
<li><p>Opening the breaker on the first failure.</p>
</li>
<li><p>Blocking threads while OPEN.</p>
</li>
<li><p>Allowing unlimited HALF_OPEN requests.</p>
</li>
<li><p>Treating all exceptions equally.</p>
</li>
<li><p>Ignoring observability.​</p>
</li>
</ul>
<p>Most of these happen when using libraries without understanding them.​</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Resilience libraries are powerful, but they are not magic. A circuit breaker is fundamentally a state machine with failure tracking and time-based transitions. Building your own – even once – forces you to internalize this reality.​</p>
<p>In Spring Boot systems, a custom circuit breaker:​</p>
<ul>
<li><p>Clarifies failure semantics.</p>
</li>
<li><p>Improves debugging.</p>
</li>
<li><p>Enables domain-specific resilience.</p>
</li>
<li><p>Makes you a better user of Resilience4j.​</p>
</li>
</ul>
<p>You may never deploy your own breaker to production. But after building one, you will never configure a circuit breaker blindly again.​</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
