Futures & Results
Futures & Results
When you submit a task to an ExecutorService, the call returns immediately — but the task keeps running in the background. The bridge between that background work and your code is a Future<V>: a handle on a computation that has not finished yet. Understanding how to retrieve, time-limit, and cancel those computations is essential for writing correct concurrent programs.
What is a Future?
java.util.concurrent.Future<V> is a generic interface with five methods. The type parameter V is the type of the result when the computation completes.
submit(Callable<V>) returns a Future<V>. The submitted Callable runs on a pool thread; future.get() blocks the calling thread until the result is ready.
The Five Methods of Future
get()— blocks indefinitely until done, then returns the result.get(long timeout, TimeUnit unit)— blocks up to the given duration; throwsTimeoutExceptionif it expires.cancel(boolean mayInterruptIfRunning)— attempts to cancel; returnsfalseif already done or un-cancellable.isCancelled()— returnstrueif cancelled before it completed.isDone()— returnstrueif finished (normally, via cancellation, or by exception).
Blocking with a Timeout
Blocking forever with get() is almost always wrong in real applications — a hung task would stall your thread indefinitely. Prefer the timeout overload:
Callable throws any checked or unchecked exception, get() wraps it in an ExecutionException. Ignoring this means silently swallowing errors. Always call e.getCause() to get the real problem.
Cancellation in Depth
The boolean passed to cancel() matters:
cancel(false)— if the task has not started yet, prevent it from ever starting; if it is already running, let it finish.cancel(true)— additionally send an interrupt signal to the thread currently running the task.
Interruption only works if the running code cooperates — it must either call a blocking method that throws InterruptedException, or periodically check Thread.currentThread().isInterrupted().
InterruptedException. Tasks that ignore interrupts cannot be cancelled cleanly.
Checked Exceptions from get()
get() declares three checked exceptions you must handle:
- InterruptedException — the waiting thread itself was interrupted. Always restore the flag:
Thread.currentThread().interrupt(). - ExecutionException — the task threw an exception. Unwrap with
getCause(). - TimeoutException (timeout overload only) — the deadline passed before the result arrived.
FutureTask: Combining Runnable and Future
FutureTask<V> implements both Runnable and Future<V>. You can wrap a Callable in it, submit it to an executor, and still hold a typed Future reference — which is useful when you need to pass the task object around before submitting it:
Polling vs Blocking
You can avoid blocking entirely if you have other work to do while waiting:
Both tasks run in parallel. You only block when you genuinely need the results, not at submission time.
CompletableFuture was introduced in Java 8. The next two lessons cover it in depth.
Summary
Future<V> gives you a handle on an asynchronous computation. Use the timeout overload of get() in production code, always handle ExecutionException by inspecting its cause, and call cancel(true) to abort runaway tasks — but only if your task cooperates with interruption. For richer composition, CompletableFuture is the next step.