They are three different roles: Runnable and Callable are the task you submit, while Future is the handle you get back to retrieve the task's eventual result.
Runnable vs Callable
Both wrap a unit of work, but they differ in return value and exceptions:
Runnable.run()returnsvoidand cannot throw a checked exception.Callable<V>.call()returns aVand may throw a checked exception.
Runnable r = () -> System.out.println("side effect only");
Callable<Integer> c = () -> {
Thread.sleep(100); // checked InterruptedException is allowed
return 42;
};
Callable was added in Java 5 specifically because Runnable could not return a result or signal failure to the submitter — the work had to stash its result in a shared field, which is awkward and error-prone.
Where Future comes in
You hand a task to an ExecutorService. What you get back depends on what you submitted:
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> f1 = pool.submit(c); // Callable -> Future<Integer>
Future<?> f2 = pool.submit(r); // Runnable -> Future<?> (get() yields null)
Future<String> f3 = pool.submit(r, "ok"); // Runnable + preset result
Integer result = f1.get(); // blocks until the Callable returns 42
Future<V> is the placeholder for a result computed asynchronously. Its contract is small: get() (blocking, optionally with timeout), isDone(), isCancelled(), and cancel().
The relationship in one line
Callable produces a value; Future delivers it. Runnable produces nothing, so its Future only tells you when it finished (or carries a result you preset).