CompletableFuture in Java 8
🔹 What is CompletableFuture?
CompletableFuture is a class introduced in Java 8 under:
java.util.concurrent.CompletableFuture
It is used for:
- Asynchronous programming
- Non-blocking execution
- Chaining multiple tasks
- Combining parallel computations
- Handling async exceptions cleanly
🔹 Why CompletableFuture Was Introduced
Before Java 8, Java had Future.
Example:
Future<String> future = executor.submit(() -> "Hello");
Problems with Future:
- Cannot chain tasks
- No callback mechanism
- Blocking calls using
get() - Challenging exception handling
Java 8 introduced CompletableFuture to solve these limitations.
🔹 Basic CompletableFuture Example
import java.util.concurrent.CompletableFuture;
public class Main {
public static void main(String[] args) {
CompletableFuture<String> future =
CompletableFuture.supplyAsync(() -> "Hello from async task");
System.out.println(future.join());
}
}
🔹 Creating CompletableFuture
1. runAsync()
Used when a task does NOT return value.
public static void main(String[] args) {
CompletableFuture.runAsync(() -> System.out.println("Running async task"));
}
2. supplyAsync()
Used when task RETURNS value.
CompletableFuture<String> future =
CompletableFuture.supplyAsync(() -> "Hello");
🔹 Chaining Operations
One of the most powerful features.
thenApply()
Transforms result.
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> 10)
.thenApply(x -> x * 2)
.thenApply(x -> x + 5)
.thenAccept(System.out::println);
}
Output:
25
thenAccept()
Consumes result.
.thenAccept(result -> System.out.println(result));
thenRun()
Run a task without input/output.
.thenRun(() -> System.out.println("Done"));
🔹 Combining Multiple Futures
thenCombine()
Combines results from two futures.
public static void main(String[] args) {
CompletableFuture<Integer> f1 =
CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> f2 =
CompletableFuture.supplyAsync(() -> 20);
f1.thenCombine(f2, (a, b) -> a + b)
.thenAccept(System.out::println);
}
Output:
30
🔹 Waiting for Multiple Futures
allOf()
Wait for all futures to complete.
CompletableFuture<Void> all =
CompletableFuture.allOf(f1, f2);
all.join();
🔹 Exception Handling
exceptionally()
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> {
if (true)
throw new RuntimeException("Error");
return 10;
})
.exceptionally(ex -> {
System.out.println("Handled: " + ex);
return 0;
});
}
handle()
Handles both success and failure.
.handle((result, ex) -> {
if (ex != null) {
return 0;
}
return result;
});
🔹 Async vs. Non-Async Methods
| Method | Execution Thread |
|---|---|
| thenApply() | Same thread |
| thenApplyAsync() | Separate thread/thread pool |
🔹 Important Methods Cheat Sheet
| Method | Purpose |
|---|---|
| runAsync() | Async task without result |
| supplyAsync() | Async task with result |
| thenApply() | Transform result |
| thenAccept() | Consume result |
| thenRun() | Execute another task |
| thenCombine() | Combine two futures |
| allOf() | Wait for all futures |
| exceptionally() | Handle exceptions |
| handle() | Handle success/failure |
🔹 Internal Working
By default, CompletableFuture uses:
ForkJoinPool.commonPool()
Unless custom Executor is provided.
Example:
ExecutorService executor =
Executors.newFixedThreadPool(5);
CompletableFuture.supplyAsync(() -> {
return "Hello";
}, executor);
🔹 Why CompletableFuture Is Powerful
Advantages:
- Non-blocking programming
- Better CPU utilization
- Parallel execution
- Cleaner async code
- Easy error handling
- Functional style programming
- Better scalability
🔹 Difference Between Future and CompletableFuture
| Feature | Future | CompletableFuture |
|---|---|---|
| Blocking support | Yes | Yes |
| Non-blocking callbacks | No | Yes |
| Task chaining | No | Yes |
| Combine futures | No | Yes |
| Exception handling | Difficult | Easy |
| Functional programming | No | Yes |
🔹 Real Microservice Use Case of CompletableFuture
Suppose we have an E-Commerce microservice.
When a user opens a product page, application needs:
- Product Details Service
- Inventory Service
- Pricing Service
- Review Service
Instead of calling sequentially:
Call Product Service
Wait
Call Inventory Service
Wait
Call Pricing Service
Wait
Call Review Service
Wait
We can call ALL services in parallel using CompletableFuture.
🔹 Real Example
import java.util.concurrent.CompletableFuture;
public class ProductAggregator {
public static void main(String[] args) {
CompletableFuture<String> productFuture =
CompletableFuture.supplyAsync(ProductAggregator::getProduct);
CompletableFuture<String> inventoryFuture =
CompletableFuture.supplyAsync(ProductAggregator::getInventory);
CompletableFuture<String> pricingFuture =
CompletableFuture.supplyAsync(ProductAggregator::getPrice);
CompletableFuture<String> reviewFuture =
CompletableFuture.supplyAsync(ProductAggregator::getReviews);
CompletableFuture.allOf(
productFuture,
inventoryFuture,
pricingFuture,
reviewFuture
).join();
System.out.println(productFuture.join());
System.out.println(inventoryFuture.join());
System.out.println(pricingFuture.join());
System.out.println(reviewFuture.join());
}
static String getProduct() {
sleep();
return "Product Details";
}
static String getInventory() {
sleep();
return "Inventory Available";
}
static String getPrice() {
sleep();
return "Price: 500";
}
static String getReviews() {
sleep();
return "Reviews: 4.5";
}
static void sleep() {
try {
Thread.sleep(2000);
} catch (Exception e) {
}
}
}
🔹 Performance Benefit
Sequential calls:
2 + 2 + 2 + 2 = 8 seconds
Parallel CompletableFuture calls:
~2 seconds total
Huge performance improvement.
🔹 Common Interview Questions
Q1. Why CompletableFuture over Future?
Because it supports:
- Chaining
- Non-blocking callbacks
- Combining tasks
- Better exception handling
Q2. Difference between thenApply and thenCompose?
thenApply()
Transforms result.
thenCompose()
Flattens nested futures.
Example:
.thenCompose(data -> getAnotherFuture(data))
Q3. Difference between join() and get()?
| join() | get() |
|---|---|
| Unchecked exception | Checked exception |
| Cleaner syntax | Requires try-catch |
🔹 Simple One-Line Definition
CompletableFuture is a Java 8 feature used for asynchronous, non-blocking programming that allows chaining, combining, and handling multiple tasks efficiently.