How do you implement Bulkheads in a microservices architecture?

The Bulkhead pattern isolates resources, such as thread pools, connection pools, or CPU/memory, per service or per dependency so that a failure or overload in one part of the system can't exhaust resources needed by the rest.

Key Points: • Each service, or even each downstream dependency within a service, gets its own dedicated thread pool or connection pool instead of sharing one global pool. • A cap on concurrent requests per dependency prevents one slow or failing downstream call from consuming all available threads. • Bulkheads are commonly implemented with libraries like Resilience4j's Bulkhead module, which limits concurrent calls to a named resource. • The pattern is named after ship bulkheads, which stop a hull breach in one compartment from flooding the entire ship. • Bulkheads work well alongside the Circuit Breaker pattern: bulkheads limit concurrency, circuit breakers stop calling a dependency that's already failing.

Example: If a Recommendations service call is slow, a bulkhead limits it to, say, 10 concurrent threads out of a much larger total pool, so even if Recommendations hangs, the Checkout flow still has threads available to serve other requests.

Code Example:

Bulkhead bulkhead = Bulkhead.of("recommendations",
    BulkheadConfig.custom()
        .maxConcurrentCalls(10)
        .maxWaitDuration(Duration.ofMillis(500))
        .build());

Supplier<List<Item>> decorated = Bulkhead.decorateSupplier(
    bulkhead, recommendationsClient::fetch);

Interview Tip: A concise interview answer is:

"I'd give each downstream dependency its own bounded pool of threads or connections, using something like Resilience4j's Bulkhead, so a slow or failing dependency can only exhaust its own slice of resources instead of starving the whole service. It pairs naturally with a circuit breaker for defense in depth."