The Bulkhead pattern — isolating resources (thread pools, semaphores, connection pools, pods/node pools, cells) so one failure can't sink the system, implementing it with Resilience4j and platform isolation, a banking example — and the Strangler Fig pattern — incremental monolith migration behind a routing facade, its benefits, a step-by-step implementation, and the real challenges (data, dual running, long transitions).
Published September 25, 2026
Both patterns are about limiting the blast radius:
Give concrete implementations at several levels: code, pods, infrastructure.
Short answer: It partitions resources (thread pools, connection pools, concurrency limits, instances), so a failure or overload in one part can't exhaust the resources that other parts need. The name comes from the watertight compartments in a ship's hull.
Example: in an airline booking system, calls to the non-critical Loyalty Points service use a small, separate pool. When Loyalty hangs, only that pool fills up. Booking keeps its own threads, and keeps selling tickets. Points are awarded later.
Learn it in depth → Bulkhead & Rate Limiting
Short answer: It replaces a monolith gradually. New microservices are built alongside it, a routing facade sends more and more functionality to them, and the monolith's corresponding code is retired, until nothing is left (the "strangler fig" vine that eventually replaces its host tree). Example: a retailer moves Cart and then Payment into services, while the rest of the monolith keeps running. That avoids a risky big-bang rewrite.
Learn it in depth → Monolith to Microservices Decomposition
Short answer: Most cascading failures are resource exhaustion. One slow dependency holds threads and connections until none are left for anything else, so the healthy features fail too. Bulkheads put a hard cap on how much of a shared resource any one dependency, tenant or feature can take:
Short answer: Implement them at several levels:
@Bulkhead(name = "loyalty", type = Bulkhead.Type.SEMAPHORE, fallbackMethod = "deferPoints")
public void awardPoints(BookingConfirmed e) { loyaltyClient.award(e.customerId(), e.points()); }
private void deferPoints(BookingConfirmed e, BulkheadFullException ex) { outbox.add(new AwardPointsLater(e)); }
resilience4j:
bulkhead:
instances:
loyalty:
max-concurrent-calls: 10 # loyalty can never hold more than 10 request threads
max-wait-duration: 0 # fail fast instead of queueing
Short answer: Online banking. The Transactions (payments), Account and Support services each have:
During salary day, PDF statement requests surge, and the PDF generator slows down. Without bulkheads, it consumes the shared request threads, and payments start timing out. With bulkheads, the PDF compartment saturates, and returns "try again later", or queues the PDFs, while payments and balance checks stay fast. Critical flows are protected by design.
Short answer: A bulkhead is resource isolation, applied deliberately to contain failures. The resources isolated include:
Key points to cover:
Short answer:
At all times, the system works, users see one application, and each step is small and reversible.
# Spring Cloud Gateway as the strangler facade
spring:
cloud:
gateway:
routes:
- id: cart-new
uri: lb://cart-service
predicates: [ "Path=/cart/**", "Weight=cart, 20" ] # 20% canary to the new service
- id: cart-legacy
uri: http://legacy-monolith
predicates: [ "Path=/cart/**", "Weight=cart, 80" ]
- id: everything-else
uri: http://legacy-monolith
predicates: [ "Path=/**" ]
Short answer:
Short answer:
Short answer:
Q: Semaphore bulkhead or thread-pool bulkhead? A: A semaphore limits concurrency on the caller's thread: low overhead, and ideal with virtual threads or reactive code. A thread-pool bulkhead runs calls on a dedicated pool: it isolates the caller's threads too, and enables timeouts on blocking calls, at the cost of context switching and queues.
Q: What is a cell-based architecture? A: The system is replicated into independent cells (each a full stack with its own data), and a thin routing layer maps users or tenants to cells. A failure, bad deployment or noisy neighbour affects one cell only. It's a bulkhead at the architecture level, used by large SaaS providers.
Q: What is the Branch by Abstraction pattern, and how does it relate to the Strangler Fig? A: It's the in-code counterpart. Introduce an abstraction around the component being replaced, move the callers onto it, build the new implementation behind it, switch over (with a feature flag), and delete the old one. It's used when the capability to extract is deep inside the monolith, rather than at the HTTP edge.
Q: How do you verify that a new service behaves like the legacy code? A: Shadow traffic: send copies of real requests to both, and diff the responses. Also characterisation tests captured from the legacy behaviour, canary metrics comparison, and data reconciliation reports during dual-running.