Callable & Future in Java
Returning results, get() blocks, cancel, ExecutionException.
A task that returns
Runnable.run() returns nothing and can't throw checked exceptions. Callable<V>.call() returns a V and **may throw Exception**. Submit a lambda that returns a value and Java treats it as a Callable.
Runnable job = () -> System.out.println("hi");
Callable<Integer> task = () -> 6 * 7;Future: a receipt
submit(callable) hands back a Future<V>: a handle to a result that may not exist yet. get() blocks until the task completes, fails, or is cancelled. It never returns null "because it isn't ready". For a non-blocking check, use isDone().
var pool = Executors.newFixedThreadPool(2);
Future<Integer> f = pool.submit(() -> 6 * 7);
doOtherWork(); // runs meanwhile
int answer = f.get(); // waits for 42When the task fails
The task divides by zero. What happens when main calls get()?
var pool = Executors.newFixedThreadPool(1);
try (pool) {
Future<Integer> f =
pool.submit(() -> 1 / 0);
System.out.println(f.get());
}Throws ExecutionExceptionThrows ArithmeticExceptionPrints null
Show the answer
get() throws **ExecutionException**. The task's own ArithmeticException was stored in the Future, and get() rethrows it wrapped. The original error is the wrapper's getCause().
Unwrap the cause
Catch ExecutionException and call getCause() to get the real failure. Printing the cause here shows java.lang.ArithmeticException: / by zero, not the wrapper.
try {
f.get();
} catch (ExecutionException e) {
Throwable real = e.getCause();
System.out.println(real);
}get() with no time limit
A request handler calls future.get() on a slow partner API. When the partner hangs, every request thread hangs with it. An unbounded get() waits forever. Use get(timeout, unit), handle TimeoutException, and fail fast.
try {
return f.get(2, TimeUnit.SECONDS);
} catch (TimeoutException e) {
f.cancel(true); // try to interrupt it
return fallback();
}What does cancel(true) do?
You call f.cancel(true) on a task that is already running. Does it stop instantly?
Think about it, then reveal the answer
Not necessarily. cancel(true) attempts to interrupt the running thread, and the task stops only if it reacts to interruption (it's cooperative). A task that hasn't started yet simply never runs. Afterwards, get() throws CancellationException.
Fan-out pages
A product page needs price, stock and reviews from three services. Submit all three, then get() each: total latency becomes the slowest call instead of the sum of all three. Put a timeout on every get() so one slow dependency can't freeze your page.
Key takeaways
- Callable.call() returns V and may throw Exception
- get() blocks; get(timeout, unit) bounds the wait
- Task failure → ExecutionException; getCause() has the real error
- cancel(true) attempts to interrupt a running task
Future arrived in Java 5 with java.util.concurrent (JSR-166), led by Doug Lea and based on his earlier util.concurrent library.
Practice questions
What does this print?
try (var pool = Executors.newSingleThreadExecutor()) {
Future<Integer> f = pool.submit(() -> 6 * 7);
System.out.println(f.get());
}- 42
- null
- 0
- java.util.concurrent.FutureTask@1b6d3586
Check your answer
42. The lambda returns a value, so it's a Callable<Integer>. get() waits for the result and returns 42.
What does this print?
try (var pool = Executors.newSingleThreadExecutor()) {
Future<Integer> f = pool.submit(() -> 1 / 0);
try {
f.get();
} catch (ExecutionException e) {
System.out.println(e.getCause());
}
}- java.lang.ArithmeticException: / by zero
- java.util.concurrent.ExecutionException
- null
- Nothing — the exception is swallowed
Check your answer
java.lang.ArithmeticException: / by zero. The task's exception is stored in the Future. get() rethrows it wrapped in an ExecutionException, and getCause() returns the original ArithmeticException.