Callable vs Runnable in Java
Fast recall
Quick revision
Core answer
Callable is like Runnable, but its call() method returns a value and can throw checked exceptions. You submit it to an ExecutorService with submit(), which returns a Future — a claim ticket you call get() on later to retrieve the result.Understanding of how to get results back from concurrent operations and handle exceptions in threads.
Not knowing how to retrieve the value from a Callable, or not understanding that Future.get() blocks until the result is ready.
Explain the Future.get() blocking behavior and mention timeout options like get(long timeout, TimeUnit unit) to avoid hanging forever.
Interview-ready guide
Interview answer
Callable is like Runnable but designed for tasks that return a result.
Instead of a run method that returns void, Callable has a call method that returns a typed value and can throw checked exceptions. You use it with an ExecutorService by calling submit, which gives you back a Future object immediately. The Future is like a claim ticket.
The task runs in the background, and you call future dot get to retrieve the result when you need it. The important thing to know is that get blocks the current thread until the result is ready, so you don't want to call it right after submit, or you lose all concurrency. The pattern is submit all your tasks first, do other work, then call get on each Future at the end.
In production, always use the timeout version of get so your code doesn't hang forever if a task gets stuck. If the Callable throws an exception, get wraps it in an ExecutionException, so you call getCause to unwrap the actual error. Use Callable when you need results back from concurrent operations, like fetching data from multiple APIs or databases.
Use Runnable for fire and forget tasks like logging or sending notifications.
Callable vs Runnable in Java
Direct Answer
Callable is like Runnable, but its call() method returns a value and can throw checked exceptions. You submit it to an ExecutorService with submit(), which returns a Future — a claim ticket you call get() on later to retrieve the result.
Code Example
import java.util.concurrent.*;
class OrderPriceCalculator implements Callable<Double> {
private final Long orderId;
OrderPriceCalculator(Long orderId) { this.orderId = orderId; }
@Override
public Double call() throws Exception {
Thread.sleep(100); // simulate an I/O delay
return orderId * 10.0; // return the calculated price
}
}
var executor = Executors.newFixedThreadPool(3);
var future1 = executor.submit(new OrderPriceCalculator(101L));
var future2 = executor.submit(new OrderPriceCalculator(102L));
try {
var price1 = future1.get(); // blocks until ready
var price2 = future2.get(5, TimeUnit.SECONDS); // timeout protection
System.out.println("Total: " + (price1 + price2));
} catch (ExecutionException e) {
System.err.println("Task failed: " + e.getCause()); // unwrap real cause
} finally {
executor.shutdown();
}
Common Mistakes & Pitfalls
- Blocking too early on
get(): Callingfuture.get()right aftersubmit()blocks until the result is ready, losing all concurrency. Submit every task first, then callget(). - Swallowing
ExecutionException: When theCallablethrows,get()wraps it. Always callgetCause()to recover the real error. - No timeout on
get(): A plainfuture.get()can hang forever. Useget(30, TimeUnit.SECONDS)to fail fast.
Check your understanding
Answer these without looking back at the article.
- What is a Future in Java?
- What does Future.get() do if the task threw an exception?
- What is ExecutorService and why use it instead of raw Thread?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.