Blocking Calls in Spring Boot – How They Kill Scalability

Blocking Calls in Spring Boot – How They Kill Scalability

Most Spring Boot applications do not fail due to CPU or memory shortages. They fail because threads are blocked waiting on I/O.

Scalability problems begin with waiting, not computation.

1. How Spring Boot Handles Requests

In a traditional Spring MVC application:

  • One HTTP request = one server thread
  • The thread is occupied until the response is returned
  • Blocking I/O keeps the thread idle but unavailable

HTTP Request
  → Tomcat Thread
      → Controller
          → Service
              → Blocking DB / HTTP / File IO
                  (Thread waiting)

2. Common Blocking Calls in Real Applications

OperationWhy It Blocks
JPA / JDBCWaiting on database response
RestTemplateSynchronous HTTP calls
Feign (sync)Thread-per-request model
Thread.sleep()Explicit thread blocking
File I/OOS-level blocking

3. The Classic Scalability Failure


@GetMapping("/orders")
public List getOrders() {
    return orderService.fetchOrders();
}

public List fetchOrders() {
    Thread.sleep(200);
    return orderRepository.findAll();
}

With 200 ms latency and 200 threads:

Maximum throughput ≈ 1000 requests/sec, even with idle CPU.

Throughput = thread count ÷ blocking time

4. Why @Async Alone Does NOT Fix Blocking


@Async
public CompletableFuture> fetchAsync() {
    return CompletableFuture.completedFuture(orderRepository.findAll());
}

This:

  • Moves blocking to another thread pool
  • Adds context switching
  • Does not increase throughput
If the work blocks, async only relocates the blockage.

5. Solution #1 – Reduce Blocking at the Source

❌ Chatty Database Access


for (Long id : ids) {
    repository.findById(id);
}

✅ Single Query


@Query("select o from Order o where o.id in :ids")
List findAllByIds(List ids);

Fewer DB calls = fewer blocked threads.


6. Solution #2 – Isolate Blocking Work

Dedicated Blocking Thread Pool


@Bean
Executor blockingExecutor() {
    return Executors.newFixedThreadPool(50);
}

@Async("blockingExecutor")
public CompletableFuture generateReport() {
    return CompletableFuture.completedFuture(reportService.generate());
}

This prevents request thread starvation.


7. Solution #3 – Virtual Threads (Correct Usage)

Enable Virtual Threads


spring.threads.virtual.enabled=true

Or Explicit Executor


@Bean
Executor taskExecutor() {
    return Executors.newVirtualThreadPerTaskExecutor();
}
Virtual Threads scale blocking I/O, not database capacity.

8. Solution #4 – Fix the Database Bottleneck


spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 10
      connection-timeout: 30000

Enable JDBC Batching


spring.jpa.properties.hibernate.jdbc.batch_size=50

Database throughput defines system throughput.


9. Solution #5 – Asynchronous Controllers

❌ Blocking Controller


@GetMapping("/data")
public List get() {
    return service.fetch();
}

✅ Non-Blocking API


@GetMapping("/data")
public CompletableFuture> get() {
    return service.fetchAsync();
}

This releases request threads earlier.


10. When Reactive Actually Makes Sense

Reactive is justified only when:

  • End-to-end non-blocking stack
  • Streaming or backpressure matters
  • R2DBC or reactive clients are used
WebFlux + JPA is worse than MVC.

11. Decision Matrix

ScenarioBest Choice
Blocking DB + MVCVirtual Threads
CPU-heavy workBounded Executor
Streaming APIsReactive
Legacy systemThread isolation

Final Takeaway

Scalability is not about more threads — it’s about less waiting.

Blocking calls fail silently. Fixing them requires understanding where your threads wait, not just where code executes.

๐Ÿšซ Eliminate Blocking Bottlenecks in Spring Boot

Blocking calls are one of the biggest scalability killers in Spring Boot applications. To design truly scalable systems, explore how threading models, database access, and Java 25 concurrency features work together.

⚡ High-Performance Spring Boot with Java 25

Learn how blocking I/O, thread pools, and JVM tuning affect real-world Spring Boot throughput.

๐Ÿ”„ Spring Boot Threading & Async Execution

Understand how Spring manages threads, executors, and @Async workloads.

๐Ÿ—„️ Spring Boot Database Performance Tuning

See how blocking JDBC calls and connection pools directly impact request throughput.

⚖️ Virtual Threads vs Spring @Async

Learn why virtual threads don’t automatically fix blocking calls in Spring Boot.

๐Ÿ“Š Java 25 Virtual Threads – Benchmarks & Pitfalls

Understand the limits of virtual threads when used with blocking I/O.

๐Ÿ› ️ Spring Boot Performance Tuning & Optimization

End-to-end tuning strategies covering memory, threads, and I/O behavior.

๐Ÿงต Java 25 Concurrency (Advanced Interviews)

Prepare for senior interviews discussing blocking vs non-blocking execution models.

๐Ÿ—️ Java System Design Interview Questions

Connect blocking call analysis with real-world scalability and architecture decisions.