Skip to main content

Command Palette

Search for a command to run...

Beyond Circuit Breakers: Why the Bulkhead Pattern Matters

Updated
4 min readView as Markdown
B
Senior Software Architect with 30+ years of experience building enterprise systems using Java, Spring Boot, and cloud-native technologies.

The Bulkhead Pattern is a resilience pattern that isolates failures so that a problem in one part of a system doesn't bring down the entire application.

The name comes from ships. A ship is divided into watertight compartments called bulkheads. If one compartment floods, the bulkheads prevent water from spreading to the rest of the ship, allowing it to stay afloat.

The same idea applies to software.


Without Bulkheads

Imagine an e-commerce application:

 Order Service
                      │
        ┌─────────────┼─────────────┐
        │             │             │
   Payment API   Inventory API   Email API

Suppose the Email API becomes very slow.

If all outbound requests share the same thread pool:

  • Email requests occupy all available threads.

  • Payment requests must wait.

  • Inventory requests must wait.

  • Eventually, the entire Order Service becomes unresponsive.

A failure in one dependency cascades to everything else.


With Bulkheads

Each dependency gets its own isolated resources.

 Order Service
                      │
     ┌──────────┬──────────┬──────────┐
     │          │          │
 Payment Pool Inventory Pool Email Pool
 10 threads    10 threads   5 threads

If the Email API hangs:

  • Only the Email Pool is exhausted.

  • Payment requests continue.

  • Inventory requests continue.

  • The application remains largely functional.


Types of Bulkheads

1. Thread Pool Isolation

Each external service has its own thread pool.

Payment API
    ↓
Thread Pool A (20)

Inventory API
    ↓
Thread Pool B (15)

Notification API
    ↓
Thread Pool C (5)

One pool cannot consume another pool's threads.


2. Connection Pool Isolation

Instead of sharing one database connection pool:

Shared Pool (100)

Use separate pools:

Customer DB
Pool: 40

Order DB
Pool: 40

Reporting DB
Pool: 20

A spike in reporting queries won't starve order processing.


3. Resource Isolation

You can isolate:

  • CPU

  • Memory

  • Network bandwidth

  • Kubernetes pods

  • Containers

  • Virtual machines

For example, in Kubernetes:

Payment Service
3 Pods

Recommendation Service
20 Pods

Search Service
5 Pods

If the Recommendation Service has issues, it doesn't directly consume the resources allocated to Payment.


Java Example (Resilience4j)

Resilience4j provides a bulkhead implementation.

BulkheadConfig config = BulkheadConfig.custom()
    .maxConcurrentCalls(10)
    .maxWaitDuration(Duration.ofMillis(100))
    .build();

Bulkhead bulkhead = Bulkhead.of("paymentService", config);

Supplier<String> decoratedSupplier =
    Bulkhead.decorateSupplier(bulkhead, paymentClient::pay);

String result = Try.ofSupplier(decoratedSupplier)
                   .recover(ex -> "Service Busy")
                   .get();

In this example:

  • Only 10 concurrent calls to the payment service are allowed.

  • Additional requests wait up to 100 ms.

  • If capacity is still unavailable, the call fails quickly rather than consuming more resources.


Bulkhead vs Circuit Breaker

These patterns are often used together, but they solve different problems.

PatternPurposeBulkheadPrevent one failing dependency from exhausting shared resources.Circuit BreakerStop calling a dependency that is already failing.

For example:

  1. The Email service becomes slow.

  2. Bulkhead confines the impact to the email resources.

  3. Circuit Breaker detects repeated failures and temporarily stops sending requests to the Email service.

  4. The rest of the application continues operating.


Bulkhead vs Rate Limiter

BulkheadRate LimiterLimits concurrent work.Limits requests over time (e.g., 100 requests per second).Protects internal resources.Protects services from excessive request rates.

You can combine them:

  • Rate Limiter controls incoming traffic.

  • Bulkhead isolates resource usage.

  • Circuit Breaker avoids repeatedly calling unhealthy services.


Real-World Example

An online shopping platform has three downstream services:

  • Payment Gateway

  • Inventory System

  • Recommendation Engine

During a flash sale:

  • The Recommendation Engine slows dramatically due to high demand.

  • Without bulkheads, recommendation requests consume shared threads, delaying payment processing and causing checkout failures.

  • With bulkheads, only the recommendation requests are affected. Customers can still browse products and complete purchases, while recommendations may be delayed or temporarily unavailable.

This prioritizes the most important business functions.


Best Practices

  • Allocate separate thread or connection pools for critical dependencies.

  • Size each pool according to expected traffic and business priority.

  • Combine bulkheads with timeouts, retries, circuit breakers, and fallback mechanisms.

  • Monitor metrics such as concurrent calls, queue lengths, rejected requests, and latency.

  • Ensure that low-priority services cannot starve mission-critical ones.

Key Takeaway

The Bulkhead Pattern is about isolation. Instead of allowing every component to compete for the same resources, you partition resources so that failures remain contained.

Think of it this way:

  • Timeout: "Don't wait forever."

  • Retry: "Try again if appropriate."

  • Circuit Breaker: "Stop calling a service that's failing."

  • Bulkhead: "Don't let one failure consume everyone else's resources."

  • Rate Limiter: "Control how much traffic enters the system."

Together, these patterns form the foundation of resilient microservice architectures.

More from this blog

B

Bill LIao's Blog

137 posts

A technical blog on modern backend development, software architecture, and practical AI agent workflows