skip to content

Callable, Future & CompletionService

Callable returns a value and may throw checked exceptions where Runnable does neither, and Future is how you retrieve, time out, or cancel that result. CompletionService is the follow-up: consuming results in completion order instead of submission order.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

What is the difference between Callable<V> and Runnable, and when would you choose one over the other?

level: juniorimportance: must knowfreq 78%

answer

  1. Runnable: void run(), no throws clause
  2. Callable<V>: V call() throws Exception
  3. Callable -> Future<V>, Runnable -> Future<?> (null)
  4. Callable added in Java 5 with executors
  5. Executors.callable(Runnable) adapts one way

basics

~10 s

Runnable.run() returns nothing and can't throw checked exceptions. Callable<V>.call() returns a value of type V and can throw checked exceptions. Use Callable when the task produces a result you need back.

solid answer

~40 s

Both represent a unit of work to run on a thread, but their signatures differ. Runnable has void run() — no return value, and it can only throw unchecked (runtime) exceptions because the signature has no throws clause. Callable<V> has V call() throws Exception — it returns a typed result and may throw checked exceptions. You submit a Callable to an ExecutorService and get back a Future<V> to retrieve the result; a Runnable can also be submitted but yields no value (Future<?> whose get() returns null, or a supplied result). Choose Callable when the task computes something you need to collect or when it can fail with a checked exception you want to propagate. Choose Runnable for fire-and-forget side-effecting work. Executors.callable(Runnable) adapts one to the other.

go deeper

for a junior

Knows the signature difference: Runnable returns void, Callable returns a value and can throw checked exceptions; picks Callable when a result is needed.

for a middle

Connects them to the executor framework: submit(Callable) -> Future<V>, submit(Runnable) -> Future<?>; knows Executors.callable adapts a Runnable.

for a senior

Explains the historical reason (Runnable predates generics/executors), the checked-exception propagation difference, and the submit(Runnable, result) overload.

for a principal

Discusses API design trade-offs of not unifying them, lambda inference ambiguity when both overloads apply, and how virtual threads / structured concurrency reuse these same interfaces.

## The problem both interfaces solve In Java, you cannot hand a thread a 'method to run' directly — you hand it an **object** whose method the thread calls. A *functional interface* (an interface with one abstract method) is how Java packages 'a piece of work'. `Runnable` and `Callable<V>` are the two standard ones for concurrency. ### Runnable ```java public interface Runnable { void run(); } ``` - **Returns nothing** (`void`). - The signature has **no `throws` clause**, so `run()` can only throw *unchecked* exceptions — `RuntimeException` or `Error`. A *checked exception* (one the compiler forces you to declare or catch, e.g. `IOException`) cannot escape `run()`; you must catch it inside. - Existed since Java 1.0; it's what `new Thread(runnable)` takes. ### Callable<V> ```java public interface Callable<V> { V call() throws Exception; } ``` - **Returns a value** of the generic type `V`. - Declares `throws Exception`, so `call()` **may throw checked exceptions** and let them propagate. - Added in Java 5 alongside the `java.util.concurrent` executor framework. ### Why two of them? Runnable predates generics and the executor framework. When Java 5 introduced thread pools that could *return results*, a return type and checked-exception support were needed — hence Callable. They are not subtypes of each other; you can't pass a Callable where a Runnable is expected. ### How you actually use them You rarely call `run()`/`call()` yourself. You submit the task to an `ExecutorService` (a thread pool): ```java ExecutorService pool = Executors.newFixedThreadPool(4); Future<Integer> f = pool.submit(() -> compute()); // Callable -> Future<Integer> pool.submit(() -> log("done")); // Runnable -> Future<?> ``` `submit(Callable<V>)` returns `Future<V>`; `submit(Runnable)` returns `Future<?>` whose `get()` yields `null` (or `submit(Runnable, T result)` returns that preset `T`). ### Adapting between them `Executors.callable(Runnable)` wraps a Runnable as a `Callable<Object>` (returning null). There is no built-in adapter the other direction that preserves the result, because a Runnable has nowhere to put one. ### Decision rule - Need a **result** back, or want to **propagate a checked exception**? → `Callable<V>`. - **Fire-and-forget** side effect, no result, no checked exception to surface? → `Runnable`.

  • Can a Runnable's run() method throw an IOException?
    No. run() has no throws clause, so only unchecked exceptions (RuntimeException/Error) can escape it. An IOException must be caught inside run() or wrapped in an unchecked exception.
  • If you submit a Runnable to an ExecutorService, what does the returned Future's get() return?
    null by default. Use the submit(Runnable, T result) overload to make get() return a preset result T instead.

Runnable is a chef told 'go cook' and you never get a plate back; Callable is a chef who hands you a finished dish (and can shout 'the oven broke!' — a checked failure you must deal with).

saying these in an interview costs you the question

  • Saying Callable extends Runnable — they are unrelated interfaces.
  • Claiming Runnable can throw checked exceptions.
  • Thinking you must call run()/call() manually instead of submitting to an executor.
  • Saying submit(Runnable) returns no Future — it returns Future<?>.

context

open as a page

How do you retrieve the result of an asynchronous task with Future, and what are the semantics of get(), the timed get(), isDone(), isCancelled(), and cancel()?

level: middleimportance: must knowfreq 80%

basics

~20 s

Future is a handle to a task's eventual result. future.get() blocks until the task finishes and returns the value (or throws if it failed). The timed get() waits at most a duration. isDone()/isCancelled() check state; cancel() tries to stop it.

open as a page

What problem does ExecutorCompletionService solve, and how does it differ from collecting a List<Future> and calling get() on each?

level: seniorimportance: should knowfreq 58%

basics

~20 s

ExecutorCompletionService hands you results in the order tasks finish, not the order you submitted them. You call take() to get the next completed Future, so a slow task can't block you from processing fast ones that already finished.

open as a page

Walk through correct exception handling and cooperative cancellation when working with Callable tasks and their Futures. What are the common mistakes?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Wrap get() to catch InterruptedException and ExecutionException, and read the real failure from getCause(). To stop a task, call cancel(true); the task only stops if its code checks the interrupt flag or calls interruptible methods. Always restore the interrupt flag.

open as a page

What are the design limitations of java.util.concurrent.Future, and how do CompletableFuture and structured concurrency address them?

level: principalimportance: nice to knowfreq 40%

basics

~20 s

Plain Future only lets you block on get() or poll isDone() — you can't attach a callback, chain steps, or combine several Futures without blocking a thread. CompletableFuture adds non-blocking composition and callbacks; structured concurrency manages a whole group of tasks with shared cancellation and scope.

open as a page