Choosing the Right Java Async Tool: Future, CompletableFuture, or FutureTask
This article compares three Java concurrency approaches—Future with Callable, CompletableFuture, and FutureTask—explaining their underlying principles, step‑by‑step usage, code examples, and trade‑offs so you can decide which asynchronous tool best fits your reporting workload.
Using Future and Callable
package com.luke.designpatterns.demo;
import java.util.concurrent.*;
public class Demo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(new Callable<Integer>() {
public Integer call() throws Exception {
// get various summaries, return result
return 42;
}
});
// get async task result
Integer result = future.get();
System.out.println("Async task result is " + result);
executor.shutdown();
}
}The Callable interface defines a single call() method that can return a value and throw checked exceptions. A Future represents the pending result of an asynchronous computation and provides get(), which blocks until the computation finishes.
Step 1 – Create Callable instance: Implement call() with the business logic (e.g., aggregating order, shipping, and fee data) and return the computed value.
Step 2 – Create an ExecutorService thread pool: Use Executors.newSingleThreadExecutor() or other factory methods such as newFixedThreadPool(int nThreads) or newCachedThreadPool().
Step 3 – Submit the task: Call executor.submit(Callable<T> task), which returns a Future<T> linked to the submitted task.
Step 4 – Asynchronous execution: The thread pool runs the Callable in a separate thread, leaving the main thread free.
Step 5 – Retrieve the result: Invoke future.get(); if the task is not yet finished, the call blocks until the result is available.
This pattern decouples task definition from execution and allows the main thread to continue processing while the heavy aggregation runs in parallel.
Using CompletableFuture
import java.util.concurrent.CompletableFuture;
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
// get various summaries, return result
return 43;
});
Integer result = future.get();
System.out.println("Async task result: " + result);
}
} CompletableFuture(introduced in Java 8) combines the concepts of a Future and a callback mechanism. It enables asynchronous execution via methods such as supplyAsync() or runAsync(), and provides a rich set of composition methods.
Asynchronous execution: supplyAsync() runs the supplied lambda in a separate thread, returning a CompletableFuture immediately.
Callback mechanism: Methods like thenApply(), thenAccept(), and thenRun() register functions that are invoked when the computation completes, allowing result processing, side‑effects, or further chaining.
Task composition: thenCombine(), thenCompose(), and thenAcceptBoth() let you combine multiple asynchronous tasks, expressing dependencies without blocking.
Exception handling: exceptionally() and handle() let you react to errors thrown during asynchronous execution.
Waiting for completion: get() is still available, but unlike the classic Future, it does not block the thread that initiated the async call because the work is already running independently.
Overall, CompletableFuture offers a non‑blocking, callback‑driven model that simplifies complex asynchronous workflows and error handling.
Using FutureTask
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
// get various summaries, return result
return 44;
});
Thread thread = new Thread(futureTask);
thread.start();
Integer result = futureTask.get();
System.out.println("Async task result: " + result);
}
} FutureTaskis a concrete implementation of both Future and Runnable. It wraps a Callable (or Runnable) into a cancellable asynchronous task.
Encapsulate the task: Pass a Callable or Runnable to the FutureTask constructor.
Execute the task: Because FutureTask implements Runnable, it can be submitted to an Executor or started directly in a new Thread.
Retrieve the result: The Future methods are available; calling futureTask.get() blocks until the computation finishes and returns the value.
Cancel the task: futureTask.cancel(boolean mayInterruptIfRunning) attempts to stop execution, causing a CancellationException on subsequent get() calls.
In practice, all four approaches— Future + Callable, CompletableFuture, FutureTask, and plain Future —can satisfy the requirement of aggregating independent data sources concurrently; the choice depends on the need for callbacks, composition, or simple blocking retrieval.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Web Project
Focused on Java backend technologies, trending internet tech, and the latest industry developments. The platform serves over 200,000 Java developers, inviting you to learn and exchange ideas together. Check the menu for Java learning resources.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
