What is blocking code and how to distinguish it from non-blocking
but internally use get()/join(), making it blocking. Conversely, a method without Async may return a CompletableFuture and be non-blocking. 4. Works with synchronous I/O
π’ Junior Level
Blocking code β code that stops the thread and waits for an operation to complete.
Non-blocking code β the thread does not wait and continues performing other tasks.
// β Blocking β thread waits
String result = future.get(); // waits for the task to complete
Thread.sleep(1000); // waits 1 second
InputStream.read(); // waits for data
// β
Non-blocking β thread is free
CompletableFuture.supplyAsync(() -> {
return "result"; // executes in another thread
}).thenAccept(result -> System.out.println(result));
// Main thread continues working
Simple analogy:
- Blocking β standing in a queue and waiting
- Non-blocking β left a request and left, they will call you back
π‘ Middle Level
Blocking operations
Blocking I/O:
// β Blocking
InputStream.read(); // waits for data
OutputStream.write(data); // waits for send
Socket.accept(); // waits for connection
Files.readAllBytes(path); // waits for read
// β
Non-blocking (NIO)
AsynchronousFileChannel.read(buffer, position, null, completionHandler);
AsynchronousSocketChannel.connect(remote);
Blocking network:
// β Blocking HTTP call
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, BodyHandlers.ofString()); // blocks!
// β
Non-blocking HTTP call
HttpClient.newHttpClient()
.sendAsync(request, BodyHandlers.ofString()) // returns CompletableFuture
.thenAccept(response -> process(response));
Database:
// β Blocking
ResultSet rs = statement.executeQuery(sql); // blocks
// β
Non-blocking (R2DBC)
Mono<Result> result = client.sql(sql).fetch().one();
How to distinguish
Signs of blocking code:
- The method does not return control immediately
- The name has no
Async(but this is not a guarantee). A method may be called doSomethingAsync but internally use get()/join(), making it blocking. Conversely, a method without Async may return a CompletableFuture and be non-blocking. - Uses
get(),join(),await(),sleep() - Works with synchronous I/O
Typical mistakes
- CompletableFuture.get() in async context:
```java
CompletableFuture
cf = fetchDataAsync();
// β Blocks! String result = cf.get();
// β Non-blocking cf.thenAccept(result -> process(result));
---
## π΄ Senior Level
### Internal Implementation
**Blocking vs Non-blocking at OS level:**
Blocking I/O:
- Thread transitions to WAIT state
- OS performs a context switch
- Thread does no useful work
Non-blocking I/O:
- Thread continues working
- Callback on I/O completion
- Event loop or epoll/select (Linux) ```
Thread states:
// BLOCKED β waiting for monitor
// WAITING β waiting for notify/Completion
// TIMED_WAITING β sleep, wait(timeout)
// Visualization:
jcmd <pid> Thread.print
// or
jstack <pid>
Architectural Trade-offs
| Approach | Pros | Cons |
|---|---|---|
| Blocking | Simple code | Low throughput |
| Non-blocking | High throughput | More complex code |
| Virtual Threads | Simple code + throughput | Java 21+ |
Edge Cases
1. Hidden blocking:
// CompletableFuture looks async, but may block
cf.thenApply(s -> {
return httpClient.sendBlocking(url); // blocks ForkJoinPool!
});
// thenApply without Async executes in the same thread as the previous stage.
// If this is ForkJoinPool.commonPool() β a blocking operation blocks one of
// the few pool threads β thread pool starvation for all commonPool tasks.
// ForkJoinPool has few threads β blocking kills performance
2. Thread pool starvation:
// β All threads blocked β new tasks are not executed
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
CompletableFuture.supplyAsync(() -> {
Thread.sleep(10000); // blocks for 10 seconds
return "done";
}, executor);
}
// First 10 tasks block all threads
// Remaining 90 wait
Performance
Blocking I/O:
- 100 threads β 100 concurrent requests
- Thread overhead: ~1MB per thread
Non-blocking I/O:
- 1 thread β 10000+ concurrent requests
- Callback overhead: ~5-10 ns
Virtual Threads:
- 100000+ threads β millions of concurrent requests
- Thread overhead: ~few hundred bytes
// (Java 21, approximate, depends on workload)
Production Experience
Identifying blocking code:
# Thread dump
jstack <pid> > threads.txt
# Look for BLOCKED, WAITING, TIMED_WAITING
# If many threads in these states β blocking code
# Profiling
jcmd <pid> VM.native_memory summary
Best Practices
// β
Async methods for I/O
httpClient.sendAsync(request, handler);
// β
Custom Executor for blocking operations
CompletableFuture.supplyAsync(blockingTask, ioExecutor);
// β
Virtual Threads (Java 21+)
Executors.newVirtualThreadPerTaskExecutor();
// β get()/join() without reason
// β Blocking operations in ForkJoinPool
// β Ignoring thread pool starvation
π― Interview Cheat Sheet
Must know:
- Blocking code stops the thread and waits (get(), sleep(), I/O read)
- Non-blocking β thread is free, callback on completion (CompletableFuture, NIO)
- Signs of blocking: get/join/sleep/await, synchronous I/O, no Async in name (but not guaranteed)
- Hidden blocking: method called Async but internally calls a blocking operation, blocking ForkJoinPool
- Thread pool starvation: all pool threads are blocked, new tasks wait
Frequent follow-up questions:
- How to distinguish blocking from non-blocking? β blocking does not return control immediately, non-blocking returns Future/CF immediately
- Does CompletableFuture.get() block? β Yes, the thread transitions to WAITING
- What is hidden blocking? β An Async method internally calls a blocking operation, blocking the ForkJoinPool
- Do Virtual Threads solve the problem? β Yes, blocking suspends the virtual thread, not the OS thread (Java 21+)
Red flags (DO NOT say):
- βget() in a CompletableFuture chain is normal practiceβ β this is blocking, destroys async
- βA method called Async means non-blockingβ β may call get/join internally
- βBlocking in ForkJoinPool is not a problemβ β thread pool starvation for all tasks
Related topics:
- [[15. Why is it important to avoid blocking operations in CompletableFuture]]
- [[12. What thread pool is used by default for async methods]]
- [[11. What is the difference between thenApply() and thenApplyAsync()]]
- [[13. How to specify your own Executor for CompletableFuture]]