Callable and Future
Table of Contents
Overview
Callable and Future work together to enable asynchronous computation with return values.
Key Concepts:
- Callable: A task that can return a result and throw checked exceptions
- Future: A placeholder for the result of an asynchronous computation
- Submitted to ExecutorService for execution
- Result retrieved via Future.get() (blocking)
This pattern was the foundation for asynchronous programming in Java before CompletableFuture arrived in Java 8.
Callable vs Runnable
Runnable (Java 1.0)
public interface Runnable {
void run(); // No return value, cannot throw checked exceptions
}
Limitations:
- Cannot return a value
- Cannot throw checked exceptions
- Result must be communicated through shared state
Callable (Java 5+)
public interface Callable<V> {
V call() throws Exception; // Returns value, can throw exceptions
}
Benefits:
- Returns a result of type V
- Can throw checked exceptions
- Cleaner error handling
Comparison Example
With Runnable:
// Need shared state to capture result
final List<Integer> result = new ArrayList<>();
final List<Exception> errors = new ArrayList<>();
Runnable task = () -> {
try {
int value = performCalculation();
result.add(value);
} catch (Exception e) {
errors.add(e);
}
};
With Callable:
// Clean return value and exception propagation
Callable<Integer> task = () -> {
return performCalculation(); // Exception automatically propagated
};
Future Interface
Future<V> represents the result of an asynchronous computation.
Core Methods:
public interface Future<V> {
// Check status
boolean isDone();
boolean isCancelled();
// Get result (blocks until available)
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
// Cancel execution
boolean cancel(boolean mayInterruptIfRunning);
}
Method Descriptions:
get(): Blocks until result is availableget(timeout, unit): Blocks with timeoutisDone(): Returns true when completed (success, exception, or cancellation)isCancelled(): Returns true if cancelled before completioncancel(): Attempts to cancel execution
Working with Callable and Future
Basic Example
import java.util.concurrent.*;
public class CallableExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
// Define a Callable task
Callable<Integer> task = () -> {
System.out.println("Task started on: " +
Thread.currentThread().getName());
Thread.sleep(2000); // Simulate long computation
return 42;
};
// Submit and get Future
Future<Integer> future = executor.submit(task);
System.out.println("Task submitted, doing other work...");
// Do other work while task executes
doOtherWork();
// Get result (blocks if not ready)
Integer result = future.get();
System.out.println("Result: " + result);
executor.shutdown();
}
static void doOtherWork() {
System.out.println("Doing other work on main thread");
}
}
Output:
Task submitted, doing other work...
Doing other work on main thread
Task started on: pool-1-thread-1
Result: 42
Multiple Callable Tasks
ExecutorService executor = Executors.newFixedThreadPool(3);
List<Callable<String>> tasks = Arrays.asList(
() -> { Thread.sleep(1000); return "Task 1"; },
() -> { Thread.sleep(2000); return "Task 2"; },
() -> { Thread.sleep(1500); return "Task 3"; }
);
// Submit all tasks
List<Future<String>> futures = new ArrayList<>();
for (Callable<String> task : tasks) {
futures.add(executor.submit(task));
}
// Collect results
for (Future<String> future : futures) {
System.out.println(future.get()); // Blocks for each
}
executor.shutdown();
Using invokeAll (Bulk Submission)
ExecutorService executor = Executors.newFixedThreadPool(3);
List<Callable<String>> tasks = Arrays.asList(
() -> { Thread.sleep(1000); return "Task 1"; },
() -> { Thread.sleep(2000); return "Task 2"; },
() -> { Thread.sleep(1500); return "Task 3"; }
);
// Submit all tasks and wait for all to complete
List<Future<String>> futures = executor.invokeAll(tasks);
// All tasks are done at this point
for (Future<String> future : futures) {
System.out.println(future.get()); // Non-blocking, already complete
}
executor.shutdown();
Using invokeAny (First to Complete)
ExecutorService executor = Executors.newFixedThreadPool(3);
List<Callable<String>> tasks = Arrays.asList(
() -> { Thread.sleep(3000); return "Slow task"; },
() -> { Thread.sleep(1000); return "Fast task"; },
() -> { Thread.sleep(2000); return "Medium task"; }
);
// Returns result of first task to complete successfully
String result = executor.invokeAny(tasks);
System.out.println(result); // "Fast task"
executor.shutdown();
Getting Results
Blocking with get()
Future<String> future = executor.submit(() -> {
Thread.sleep(5000);
return "Result";
});
// Blocks for up to 5 seconds
String result = future.get();
get() with Timeout
Future<String> future = executor.submit(() -> {
Thread.sleep(5000);
return "Result";
});
try {
// Wait max 2 seconds
String result = future.get(2, TimeUnit.SECONDS);
System.out.println(result);
} catch (TimeoutException e) {
System.out.println("Task timed out!");
future.cancel(true); // Cancel the slow task
}
Polling with isDone()
Future<Integer> future = executor.submit(() -> {
Thread.sleep(2000);
return 100;
});
// Non-blocking poll
while (!future.isDone()) {
System.out.println("Task still running...");
Thread.sleep(500);
}
Integer result = future.get(); // Won't block, already done
System.out.println("Result: " + result);
Cancellation and Interruption
Canceling a Task
Future<String> future = executor.submit(() -> {
for (int i = 0; i < 10; i++) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Task was interrupted");
return "Cancelled";
}
Thread.sleep(1000);
System.out.println("Working... " + i);
}
return "Completed";
});
// Cancel after 3 seconds
Thread.sleep(3000);
boolean cancelled = future.cancel(true); // true = interrupt if running
System.out.println("Cancelled: " + cancelled);
try {
future.get(); // Throws CancellationException
} catch (CancellationException e) {
System.out.println("Task was cancelled");
}
cancel() Parameters:
cancel(false): Task won’t start if queued, but won’t interrupt if runningcancel(true): Attempts to interrupt running task
Handling Interruption in Task
Callable<String> task = () -> {
try {
for (int i = 0; i < 100; i++) {
// Check for interruption
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException("Task interrupted");
}
// Do work
performWork(i);
}
return "Success";
} catch (InterruptedException e) {
System.out.println("Cleaning up after interruption");
return "Interrupted";
}
};
Limitations
Despite being useful, Callable and Future have significant limitations that led to CompletableFuture:
1. Blocking API
// get() blocks the calling thread
String result = future.get(); // Must wait!
No way to attach a callback or continue with a non-blocking operation.
2. No Chaining
// Cannot chain operations
Future<Integer> future1 = executor.submit(() -> 10);
// How to transform result to String? Need manual code:
Integer result = future1.get();
Future<String> future2 = executor.submit(() -> String.valueOf(result));
3. No Combining
// Cannot easily combine results from multiple futures
Future<Integer> f1 = executor.submit(() -> 10);
Future<Integer> f2 = executor.submit(() -> 20);
// Manual combination needed
Integer sum = f1.get() + f2.get(); // Blocks twice
4. Limited Exception Handling
try {
String result = future.get();
} catch (ExecutionException e) {
// Wrapped exception, needs unwrapping
Throwable cause = e.getCause();
}
5. No Completion Notification
No built-in way to be notified when computation completes without polling or blocking.
Solution: Use CompletableFuture for advanced asynchronous patterns (Java 8+).
Future Execution Flow
Client: return Future note over Client: Can do other work
Executor -> WorkerThread: assign task WorkerThread -> WorkerThread: execute callable.call()
alt Task completes successfully WorkerThread -> Future: set result(value) Client -> Future: get() Future –> Client: return value else Task throws exception WorkerThread -> Future: set exception(e) Client -> Future: get() Future –> Client: throw ExecutionException else Task is cancelled Client -> Future: cancel(true) Future -> WorkerThread: interrupt() WorkerThread -> Future: set cancelled Client -> Future: get() Future –> Client: throw CancellationException end @enduml –>
Ref.
Official Documentation:
Guides:
- Baeldung: Callable and Future Guide
- Java Concurrency in Practice - Chapter on task execution
Next Steps:
- For modern asynchronous programming, see CompletableFuture
- For thread pool management, see Executors
Get Started | Java Concurrency | Java 8