What is the difference between Callable<V> and Runnable, and when would you choose one over the other?
answer
- Runnable: void run(), no throws clause
- Callable<V>: V call() throws Exception
- Callable -> Future<V>, Runnable -> Future<?> (null)
- Callable added in Java 5 with executors
- Executors.callable(Runnable) adapts one way
basics
~10 sRunnable.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 sBoth 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
Knows the signature difference: Runnable returns void, Callable returns a value and can throw checked exceptions; picks Callable when a result is needed.
Connects them to the executor framework: submit(Callable) -> Future<V>, submit(Runnable) -> Future<?>; knows Executors.callable adapts a Runnable.
Explains the historical reason (Runnable predates generics/executors), the checked-exception propagation difference, and the submit(Runnable, result) overload.
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<?>.