0Pricing
Spring Boot 4 Complete Guide · Pelajaran

Circuit Breaker dan Isolasi Bulkhead

Lindungi panggilan ke layanan hilir dengan circuit breaker, bulkhead, dan metode fallback Resilience4j.

Circuit Breaker dan Isolasi Bulkhead adalah pelajaran Spring Boot 4 Complete Guide gratis di CoddyKit. Ini adalah pelajaran 2 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar Spring Boot 4 Complete Guide, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus Spring Boot 4 Complete Guide mencakup 4 pelajaran total.

Bagian dari pelajaran ini belum diterjemahkan dan ditampilkan dalam bahasa Inggris.

Why Resilience Matters

In a distributed system, a single slow or failing downstream service can cascade into a total outage. If your service keeps calling a dead dependency, threads pile up waiting on timeouts until the whole application becomes unresponsive.

Resilience engineering is about containing failure. Two foundational patterns:

  • Circuit Breaker — stop calling a failing dependency for a while, fail fast instead of waiting.
  • Bulkhead — isolate resources so one saturated dependency cannot exhaust the threads or connections needed by the rest.

In Spring Boot 4 we implement these with Resilience4j, a lightweight, functional fault-tolerance library that replaced the now end-of-life Hystrix.

Adding Resilience4j

Spring Boot 4 integrates Resilience4j through the resilience4j-spring-boot3 starter (compatible with Boot 3.x/4.x) plus Spring AOP. The annotations are activated by an aspect that wraps your method calls.

You also pull in spring-boot-starter-aop and, for metrics, spring-boot-starter-actuator with Micrometer.

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

How a Circuit Breaker Works

A circuit breaker is a state machine that monitors the failure rate of calls:

  • CLOSED — calls flow through normally. Failures are recorded in a sliding window.
  • OPEN — once the failure rate crosses a threshold, the breaker trips. Further calls fail immediately (no downstream call) for a wait duration.
  • HALF_OPEN — after the wait, a limited number of trial calls are permitted. If they succeed, it returns to CLOSED; if they fail, it returns to OPEN.

The key benefit: when a dependency is down, you fail fast instead of blocking threads on doomed calls, and you give the dependency room to recover.

Annotating a Protected Call

The @CircuitBreaker annotation wraps a method. The name ties it to a configuration instance, and fallbackMethod names a method to invoke when the call fails or the breaker is open.

The fallback must have the same signature plus a trailing Throwable (or a more specific exception) parameter.

@Service
public class PaymentClient {

    private final RestClient restClient;

    public PaymentClient(RestClient restClient) {
        this.restClient = restClient;
    }

    @CircuitBreaker(name = "paymentService", fallbackMethod = "fallbackCharge")
    public ChargeResult charge(ChargeRequest request) {
        return restClient.post()
                .uri("/charges")
                .body(request)
                .retrieve()
                .body(ChargeResult.class);
    }

    private ChargeResult fallbackCharge(ChargeRequest request, Throwable t) {
        return ChargeResult.deferred(request.id(), "payment temporarily unavailable");
    }
}

Configuring the Breaker

Circuit breaker behavior is tuned in application.yml. You define default settings and per-instances overrides keyed by the name you used in the annotation.

  • sliding-window-type — COUNT_BASED or TIME_BASED.
  • failure-rate-threshold — percent of failures to open the breaker.
  • wait-duration-in-open-state — how long to stay OPEN before HALF_OPEN.
  • permitted-number-of-calls-in-half-open-state — trial calls allowed.
  • slow-call-duration-threshold / slow-call-rate-threshold — treat slow calls as failures.
resilience4j:
  circuitbreaker:
    configs:
      default:
        sliding-window-type: COUNT_BASED
        sliding-window-size: 20
        failure-rate-threshold: 50
        slow-call-duration-threshold: 2s
        slow-call-rate-threshold: 80
        wait-duration-in-open-state: 10s
        permitted-number-of-calls-in-half-open-state: 5
        automatic-transition-from-open-to-half-open-enabled: true
    instances:
      paymentService:
        base-config: default
        failure-rate-threshold: 40

Modeling the State Machine in Plain Java

To internalize the logic, here is a stripped-down, framework-free model of the CLOSED to OPEN transition using a count-based window. This is conceptually what Resilience4j does for you behind the annotation.

Run it to see the breaker trip once the failure rate crosses the threshold.

public class MiniBreaker {
    enum State { CLOSED, OPEN }
    static State state = State.CLOSED;
    static int window = 10, threshold = 50;
    static boolean[] results = new boolean[window];
    static int idx = 0, count = 0;

    static void record(boolean failure) {
        results[idx] = failure;
        idx = (idx + 1) % window;
        if (count < window) count++;
        int fails = 0;
        for (int i = 0; i < count; i++) if (results[i]) fails++;
        int rate = count == 0 ? 0 : (fails * 100 / count);
        if (count == window && rate >= threshold) state = State.OPEN;
    }

    public static void main(String[] args) {
        boolean[] calls = {false,true,false,true,true,false,true,true,false,true};
        for (boolean fail : calls) {
            record(fail);
            System.out.println((fail ? "FAIL" : "OK  ") + " -> state=" + state);
        }
    }
}

Counting Records vs Ignoring Exceptions

Not every exception should trip the breaker. A 400 Bad Request means your input was wrong, not that the dependency is unhealthy — counting it as a failure would open the breaker for valid traffic.

Use record-exceptions to list throwables that count as failures, and ignore-exceptions for those that should pass through without affecting breaker state.

resilience4j:
  circuitbreaker:
    instances:
      paymentService:
        base-config: default
        record-exceptions:
          - java.io.IOException
          - java.util.concurrent.TimeoutException
          - org.springframework.web.client.HttpServerErrorException
        ignore-exceptions:
          - com.example.payments.InvalidCardException
          - org.springframework.web.client.HttpClientErrorException$BadRequest

Bulkhead Isolation

The bulkhead pattern (named after a ship's watertight compartments) limits how many concurrent calls a dependency can consume, so one slow service cannot drain the entire thread pool.

Resilience4j offers two flavors:

  • SemaphoreBulkhead — caps concurrent calls on the caller's thread. Lightweight, no extra threads.
  • ThreadPoolBulkhead — runs calls on a dedicated, bounded thread pool with a queue. Provides true isolation and only works with asynchronous returns (CompletableFuture).

If the bulkhead is full, the call is rejected with BulkheadFullException, which your fallback can handle.

Applying a Bulkhead

The @Bulkhead annotation limits concurrency. With type = THREADPOOL the method must return a CompletableFuture and runs on the bulkhead's own pool. You can stack it with @CircuitBreaker — the annotations compose.

@Service
public class InventoryClient {

    private final RestClient restClient;

    public InventoryClient(RestClient restClient) {
        this.restClient = restClient;
    }

    @CircuitBreaker(name = "inventory", fallbackMethod = "fallbackStock")
    @Bulkhead(name = "inventory", type = Bulkhead.Type.THREADPOOL)
    public CompletableFuture<StockLevel> getStock(String sku) {
        return CompletableFuture.completedFuture(
            restClient.get().uri("/stock/{sku}", sku)
                      .retrieve().body(StockLevel.class));
    }

    private CompletableFuture<StockLevel> fallbackStock(String sku, Throwable t) {
        return CompletableFuture.completedFuture(StockLevel.unknown(sku));
    }
}

Configuring Bulkheads

Semaphore and thread-pool bulkheads have separate config sections.

  • bulkhead (semaphore): max-concurrent-calls and max-wait-duration (how long a call waits for a permit before being rejected).
  • thread-pool-bulkhead: max-thread-pool-size, core-thread-pool-size, and queue-capacity.

Right-size these to the dependency's real capacity: a bulkhead larger than the downstream can handle defeats the purpose.

resilience4j:
  bulkhead:
    instances:
      paymentService:
        max-concurrent-calls: 25
        max-wait-duration: 50ms
  thread-pool-bulkhead:
    instances:
      inventory:
        core-thread-pool-size: 8
        max-thread-pool-size: 16
        queue-capacity: 20

Ordering, Fallbacks, and Observability

When multiple Resilience4j annotations decorate one method, they apply in a fixed aspect order (highest precedence first): Bulkhead → TimeLimiter → RateLimiter → CircuitBreaker → Retry. So Retry wraps the circuit breaker — a retried call that still fails counts toward the breaker.

Fallback design rules:

  • Keep fallbacks fast and side-effect-free; never call the failing dependency again.
  • Return degraded-but-valid data (cached value, default, queued-for-later).
  • Inspect the Throwable to distinguish CallNotPermittedException (breaker open) from BulkheadFullException (overloaded).

Actuator exposes state and metrics at /actuator/circuitbreakers and via Micrometer (resilience4j_circuitbreaker_state), so you can alert on OPEN breakers.

private ChargeResult fallbackCharge(ChargeRequest request, CallNotPermittedException ex) {
    return ChargeResult.deferred(request.id(), "breaker open");
}

private ChargeResult fallbackCharge(ChargeRequest request, BulkheadFullException ex) {
    return ChargeResult.rejected(request.id(), "system busy");
}

private ChargeResult fallbackCharge(ChargeRequest request, Throwable t) {
    return ChargeResult.deferred(request.id(), "payment unavailable");
}

Quick Check

Test your understanding of bulkhead isolation.

Recap

You learned how to protect downstream calls in Spring Boot 4 with Resilience4j:

  • Circuit breakers fail fast via the CLOSED → OPEN → HALF_OPEN state machine, tuned with sliding-window size, failure-rate and slow-call thresholds, and wait duration.
  • Use record-exceptions / ignore-exceptions so client errors (4xx) don't trip the breaker.
  • Bulkheads cap concurrency — SemaphoreBulkhead on the caller thread, ThreadPoolBulkhead for true isolation with CompletableFuture.
  • Fallback methods share the signature plus a trailing Throwable; return degraded-but-valid data and never re-call the failing dependency.
  • Annotation order is Bulkhead → TimeLimiter → RateLimiter → CircuitBreaker → Retry; observe state through Actuator and Micrometer.

Together these patterns keep one failing dependency from taking down your whole service.

Pertanyaan yang Sering Diajukan

Apakah pelajaran “Circuit Breaker dan Isolasi Bulkhead” gratis?

Ya — teks lengkap “Circuit Breaker dan Isolasi Bulkhead” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus Spring Boot 4 Complete Guide, upgrade ke CoddyKit PRO. Kursus Spring Boot 4 Complete Guide mencakup 4 pelajaran total.

Apa yang akan aku pelajari di “Circuit Breaker dan Isolasi Bulkhead”?

Lindungi panggilan ke layanan hilir dengan circuit breaker, bulkhead, dan metode fallback Resilience4j. Kamu berlatih Spring Boot 4 Complete Guide dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.

Apakah aku perlu pengalaman untuk memulai Spring Boot 4 Complete Guide?

Tidak diperlukan pengalaman sebelumnya. Spring Boot 4 Complete Guide di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 2 dari 4.

Berapa lama pelajaran “Circuit Breaker dan Isolasi Bulkhead” memakan waktu?

Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.

Bisakah aku menulis dan menjalankan kode dalam pelajaran Spring Boot 4 Complete Guide ini?

Ya. Setiap pelajaran Spring Boot 4 Complete Guide menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.

Semua pelajaran dalam kursus ini

  1. Propagasi Konteks dan Instrumentasi Span
  2. Circuit Breaker dan Isolasi Bulkhead
  3. Pembatas Laju, Percobaan Ulang, dan Pembatas Waktu
  4. Menghubungkan Log, Metrik, dan Pelacakan
← Kembali ke Spring Boot 4 Complete Guide