← Back to list

Understanding the Bulkhead Pattern in Microservices-Handling Concurrent Fund Transfers with Virtual…

A practical deep-dive into how modern microservices handle 1000 concurrent requests using Bulkhead, Circuit Breaker, Java 21 Virtual…

Sudha Subramaniam in Level Up Coding · 2026-03-06 15:55 · 71 claps · 4.6 min read paywalled
#bulkhead-pattern #circuit-breaker #java-virtual-threads #resiliency #microservices
Open on Medium ↗

Understanding the Bulkhead Pattern in Microservices-Handling Concurrent Fund Transfers with Virtual Threads, Kafka, and Kubernetes

A practical deep-dive into how modern microservices handle 1000 concurrent requests using Bulkhead, Circuit Breaker, Java 21 Virtual Threads, Kafka, and Kubernetes scaling.

When I first hear about the Bulkhead pattern, the explanation usually sounds simple

Limit how many calls go to a dependency

But once I try to apply this concept in a real microservice system, especially something critical like a banking fund transfer, several practical questions immediately come up in my mind

In microservices, aren’t services already isolated? Why do we still need Bulkhead?

In modern architectures where we use asynchronous queues like Kafka, do we still need Bulkhead at all?

With Java 21 Virtual Threads, doesn’t the thread exhaustion problem go away?

If 200 users hit the API and only 20 fraud checks are allowed, aren’t the other users effectively seeing a failure?

In this article, we will walk through a simple banking fund transfer architecture and see where Bulkhead, Circuit Breaker, Virtual Threads, async messaging, and Kubernetes pod scaling fit together.

Assume we have a Transfer Service running as a microservice.

It is deployed on Tomcat with a fixed thread pool.

Example configuration

Tomcat maxThreads = 200

Now imagine this scenario

1000 users initiate fund transfers at the same time

What happens inside the server?

1000 incoming requests 
| 
200 active threads 
800 waiting in request queue

Tomcat can process only 200 requests at a time. The remaining requests must wait in the connection queue.

Horizontal Scaling with Kubernetes

In modern systems, services usually run inside Kubernetes clusters. Instead of one instance, multiple pods handle the load.

Example

Transfer Service Pods = 3 Threads per pod = 200

Total capacity becomes

3 pods × 200 threads = 600 concurrent requests

If traffic increases, Horizontal Pod Autoscaler (HPA) automatically scales the service.

Example scaling event

Pods scale from 3 → 5

New capacity

5 pods × 200 threads = 1000 concurrent requests

This is how cloud systems handle large traffic spikes.

However, scaling alone does not protect the system from slow or failing dependencies.

Typical Fund Transfer Flow

A simplified synchronous flow might look like this

Mobile App 
|
POST /transfer 
|
+---- Validate Account 
+---- Fraud Check 
+---- Check Balance 
+---- Debit Sender Account 
+---- Initiate Transfer

The API responds to the user:

Transfer Initiated
Transaction ID: TX123

But remember — a real transfer is not just debit. The money must also be credited to the receiver account, often in another bank. That part usually happens asynchronously.

Asynchronous Processing

After debiting the sender account, the system publishes an event.

TransferInitiated Event
        |
        v
       Kafka

Downstream services process the transfer.

Payment Processor
      |
      +---- Payment Network
      |
      +---- Receiving Bank 
      |
      +---- Credit Receiver Account 

Once the receiver bank successfully credits the account, another event is generated.

TransferCompleted Event

Other systems react asynchronously

Send SMS
Send Email
Update Analytics
Update Loyalty Points

This design ensures that slow downstream systems do not block the user-facing API.

Why Microservices Alone Are Not Enough

A common assumption is:

Microservices already provide isolation.

Microservices isolate services, but not resources inside a service.

Example

Transfer Service 
| 
+---- Fraud Service 
+---- Payment Network 
+---- Notification Service

If Fraud Service becomes slow, it can still consume all threads inside Transfer Service.

Bulkheads isolate resource usage per dependency.

The Real Problem: Slow Dependencies

Now imagine the Fraud Service becomes slow.

Normally

Fraud check = 50 ms

But during heavy load

Fraud check = 5 seconds

Now all incoming requests call Fraud Service.

Transfer Service
   |
200 Tomcat threads
   |
Waiting on Fraud Service

What happens next?

  • All threads become blocked
  • New requests cannot start
  • Even unrelated APIs may stop responding

The service appears down, even though the code itself is fine. This is known as a cascading failure.

Bulkhead Pattern to the Rescue

The Bulkhead pattern limits how many requests can access a dependency.

Example configuration:

Fraud Service concurrency limit = 20

Now when 200 requests arrive:

20 requests → allowed to call Fraud Service
180 requests → queued or throttled

The key idea is

A slow dependency should never consume all system resources.

Addressing the Common Concern

One natural question is:

If only 20 fraud calls are allowed, do the other 180 users see failures?

Not necessarily. Systems usually implement one of three approaches.

Queueing

Throttling

Prioritization

Queueing

20 processing
180 waiting

As soon as one fraud check finishes, another request starts.

Throttling

Extra requests are rejected temporarily.

HTTP 429
Please retry shortly

Prioritization

Important transactions are processed first.

Example

High-value transfers → immediate
Low-value transfers → delayed

This ensures the system remains stable instead of collapsing completely.

Circuit Breaker: Another Layer of Protection

Bulkhead limits concurrency.

Circuit Breaker protects against repeated failures.

Example scenario:

Fraud Service keeps failing

After multiple failures:

Circuit breaker opens

Now the system stops calling Fraud Service temporarily and returns a fallback response.

Example

Fraud system unavailable
Please retry later

After some time, the circuit closes and normal calls resume.

Where Virtual Threads Fit In

Java 21 introduced Virtual Threads through Project Loom.

Traditional servers use platform threads.

Example

200 platform threads

Virtual threads allow thousands of lightweight threads.

Example

10,000 virtual threads

This improves concurrency and resource efficiency.

However, virtual threads do not protect external dependencies.

If Fraud Service becomes slow:

10,000 virtual threads could call it simultaneously

This might overwhelm the Fraud system even faster.

So virtual threads improve scalability, not resilience.

Bulkheads are still necessary.

Putting Everything Together

A resilient banking transfer architecture typically looks like this

  • Kubernetes Pod Scaling: Pods scale horizontally (e.g., 3 → 6 → 10) to distribute load across instances. Purpose: handle traffic spikes. Important: scaling solves high traffic, not downstream failures.
  • Virtual Threads: From Project Loom allow thousands of lightweight threads per pod for high concurrency. Purpose: efficiently handle many blocking requests. Important: they don’t protect dependencies.
  • Bulkhead Pattern: Limits concurrent calls to services (e.g., FraudService 20 calls). Purpose: isolate failures. Important: one slow service cannot consume all resources.
  • Circuit Breaker Pattern: Stops repeated failing calls. Purpose: prevent cascading failures. Important: system fails fast instead of waiting for long timeouts.
  • Synchronous Flow: User → API Gateway → Transfer Service → Fraud → Balance → Debit. Purpose: complete critical validation before response.
  • Asynchronous Flow: Event published to Apache Kafka. Purpose: decouple services and improve scalability.
  • Async Processing: Kafka consumers handle payment network and receiving bank credit. Important: user does not wait for these steps.
  • Notifications: TransferCompleted event triggers SMS, Email, and Analytics. Purpose: notify users and update downstream systems.

If you found this helpful, don’t forget to give this article a clap 👏 and follow me for more tips and insights! Your support means a lot.


메타데이터
post_id
bb289a1071af
slug
understanding-the-bulkhead-pattern-in-microservices-handling-concurrent-fund-transfers-with-virtual-bb289a1071af
url
https://levelup.gitconnected.com/understanding-the-bulkhead-pattern-in-microservices-handling-concurrent-fund-transfers-with-virtual-bb289a1071af
canonical_url
https://levelup.gitconnected.com/understanding-the-bulkhead-pattern-in-microservices-handling-concurrent-fund-transfers-with-virtual-bb289a1071af
author_url
https://medium.com/@sudhass
status
ok
fetched_at
2026-06-15 22:55:51