Callable and Runnable
`Callable<V>` represents a task that produces a result and may throw checked exceptions, while `Runnable` represents a task whose `run()` method does not return a value. Both can be submitted to an `ExecutorService` to run asynchronously.
`Runnable` is a functional interface with a `void run()` method. It is appropriate when the task exists for its side effects, such as updating state, writing data, or performing I/O without returning a computed value.
`Callable<V>` is a functional interface with a `V call()` method. The generic type specifies the task's result, and `call()` may declare checked exceptions, making it useful when the caller needs a computed value or explicit exception propagation.
When submitted to an `ExecutorService`, a `Runnable` can be executed with `execute()` or submitted with `submit()`. A `Callable<V>` is submitted with `submit()`, which returns a `Future<V>` representing the eventual result.
For `submit(Runnable)`, the returned `Future` does not contain a meaningful task result; calling `get()` yields `null` after successful completion. For `submit(Callable<V>)`, `Future.get()` returns the value produced by `call()` or reports failure through an exception.
Key points
- `Runnable` has `void run()` and represents work without a result.
- `Callable<V>` has `V call()` and represents work that returns a value.
- `Callable.call()` can declare checked exceptions; `Runnable.run()` cannot.
- `ExecutorService.submit(Callable)` returns a `Future<V>` for the result.
- `ExecutorService.execute(Runnable)` starts work without returning a `Future`.
Interview questions
What is the difference between `Runnable` and `Callable` in Java?
`Runnable` represents work that returns no value through `run()`. `Callable<V>` represents work that returns a value of type `V` through `call()`. `Callable` can also declare checked exceptions, whereas `Runnable.run()` cannot. With an executor, a `Callable` is normally submitted and its result is obtained from a `Future<V>`.
How do you submit a `Callable` to an executor and retrieve its result?
Create or obtain an `ExecutorService` and pass the `Callable` to `submit()`. The method returns a `Future<V>`. Calling `get()` waits for completion if necessary and then returns the value from `call()`. If the task fails, `get()` reports the failure through an `ExecutionException`.
When would you choose `Runnable` instead of `Callable`?
Use `Runnable` when the task's purpose is its side effects and the caller does not need a computed result. Examples include logging, sending a notification, or updating shared state. If the caller needs a value from the task, `Callable<V>` is generally a better fit. The choice should reflect whether the task has a meaningful return value and exception contract.
What happens when a `Callable` throws an exception?
If the `Callable` throws while running, the executor records the task failure in its `Future`. A caller invoking `Future.get()` receives an `ExecutionException` whose cause is the exception thrown by the task. The exception is therefore not normally thrown synchronously by `submit()`. Cancellation and interruption can also cause `get()` to report other exceptional conditions.
What is the difference between `execute()` and `submit()` for executor tasks?
`execute()` accepts a `Runnable` and provides no `Future`, so the caller cannot use that API to await completion, cancel the task, or retrieve a result. `submit()` accepts `Runnable` or `Callable` tasks and returns a `Future`. That future provides lifecycle operations such as waiting and cancellation and, for `Callable`, result retrieval. The choice depends on whether the caller needs task tracking or a result.