HuntExams Academy logo
JavaIntermediate#concurrency

What is the difference between Callable and Runnable?

Answer

Runnable.run() returns void and cannot throw checked exceptions. Callable.call() returns a value and can throw checked exceptions; used with ExecutorService for Futures.

Example

Callable<Integer> task = () -> { return 42; };
Runnable r = () -> System.out.println("no return value");

Detailed explanation

Runnable and Callable are functional interfaces that describe tasks, but their contracts differ in return values and exception handling. Runnable declares void run(), so its task method produces no result and cannot declare checked exceptions. Callable<V> declares V call() throws Exception, so it can return a value of type V and propagate checked exceptions. Both can throw unchecked exceptions. For example, Runnable r = () -> System.out.println("no return value"); performs an action, while Callable<Integer> task = () -> 42; computes an integer.

Neither interface creates a thread or guarantees asynchronous execution. Calling r.run() or task.call() directly executes the method on the current thread. To schedule work, you pass a task to a threading API. A Thread constructor accepts Runnable, but not Callable directly. ExecutorService supports both: execute(Runnable) schedules a task without returning a Future, whereas submit(Runnable) and submit(Callable<V>) return Futures. These Futures provide a way to wait for completion, observe failure, or request cancellation without tying task definitions to thread management.

Under the hood, typical executor implementations wrap submitted tasks in a RunnableFuture, commonly FutureTask, which combines an executable task with storage for its completion state and outcome. For a Callable<V>, Future<V>.get() returns the value produced by call(). For submit(Runnable), get() returns null after successful completion; submit(Runnable, result) instead returns the supplied result. get() blocks if the task is unfinished. If a submitted task throws an exception, get() reports the failure through ExecutionException, whose cause is the original exception. The caller must also handle InterruptedException if interrupted while waiting.

In interviews, the important distinction is the task contract, not a claim that Callable is inherently more concurrent or faster. Use Runnable when only the action matters, and Callable when the task naturally produces a result or needs to propagate checked exceptions. Explain that a Runnable must catch checked exceptions within run() or translate them into unchecked exceptions. Also distinguish submission from retrieval: submit() schedules work and returns a Future, while get() may block the calling thread.

Key points to remember

  • Runnable.run() returns void; Callable<V>.call() returns V.
  • Callable.call() permits checked exceptions; Runnable.run() does not.
  • Both interfaces support lambdas, and neither starts a thread by itself.
  • Thread accepts Runnable directly; ExecutorService.submit() accepts either interface.
  • Future.get() retrieves results and exposes task failures through ExecutionException.

Common mistakes candidates make

  • Saying Runnable cannot throw any exceptions: it can throw unchecked exceptions.
  • Claiming Runnable cannot be used with Futures: submit(Runnable) returns a Future<?>.
  • Assuming Callable runs asynchronously by itself or that Future.get() never blocks.

Likely follow-up questions

Can a Callable run using a Thread without an executor?

Yes: wrap it in a FutureTask, then pass that wrapper to Thread, for example FutureTask<Integer> future = new FutureTask<>(() -> 42); new Thread(future).start();. Calling future.get() retrieves the result because FutureTask implements both Runnable and Future.

What happens when a submitted Runnable throws an exception?

Its Future records the failure, and get() throws ExecutionException with the original exception as its cause. The exception is not automatically thrown on the thread that called submit(), so ignoring the Future can leave failures unnoticed.

How does cancellation work with a Future?

cancel(true) attempts cancellation and may interrupt the executing thread; it does not guarantee that running code stops. Tasks should cooperate with interruption, and get() throws CancellationException if the Future was successfully cancelled.

Interview tip: Start with return type and checked exceptions, then compare submit() and get() using one short example.

Related Interview Questions

1
JavaAdvanced#concurrency

Explain wait(), notify() and notifyAll().

Open
2
JavaAdvanced#design-patterns

What is the Singleton pattern? How to implement it thread-safely?

Open
3
JavaIntermediate#design-patterns#spring

What is dependency injection?

Open