Submitting Tasks: Runnable vs Callable
Submit Runnable and Callable tasks, and retrieve results via Future.
Submitting Tasks: Runnable vs Callable is a free Java Academy lesson on CoddyKit. This is lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, and your progress syncs across the web and the CoddyKit app. The Java Academy course includes 4 lessons in total.
Runnable vs Callable
Two functional interfaces represent tasks submitted to ExecutorService:
- Runnable: no return value, cannot declare checked exceptions
- Callable<V>: returns a result of type V, can throw checked exceptions
import java.util.concurrent.*;
// Runnable — no return value
Runnable r = () -> System.out.println("Running");
// Callable<Integer> — returns a value
Callable<Integer> c = () -> {
return 42; // or do computation
};submit(Runnable)
submit(Runnable) returns a Future<?>. Calling get() on it returns null but confirms the task completed (or throws if it failed).
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<?> future = pool.submit(() ->
System.out.println("Task done on " + Thread.currentThread().getName())
);
future.get(); // waits for completion; returns null
System.out.println("Task confirmed complete");
pool.shutdown();submit(Callable<V>)
submit(Callable) returns a Future<V>. Call future.get() to retrieve the result (blocks until complete).
ExecutorService pool = Executors.newFixedThreadPool(2);
Callable<Integer> sumTask = () -> {
int sum = 0;
for (int i = 1; i <= 100; i++) sum += i;
return sum;
};
Future<Integer> future = pool.submit(sumTask);
Integer result = future.get(); // blocks until done
System.out.println("Sum 1..100 = " + result); // 5050
pool.shutdown();Submitting Multiple Callables with invokeAll
invokeAll() submits a collection of Callables and returns a List of Futures — all complete before returning:
List<Callable<String>> tasks = List.of(
() -> "Result A",
() -> "Result B",
() -> "Result C"
);
ExecutorService pool = Executors.newFixedThreadPool(3);
List<Future<String>> futures = pool.invokeAll(tasks);
for (Future<String> f : futures) {
System.out.println(f.get());
}
pool.shutdown();invokeAny: First Successful Result
invokeAny() submits a collection of Callables and returns the result of the first one that completes successfully, cancelling the rest:
List<Callable<String>> candidates = List.of(
() -> { Thread.sleep(200); return "Slow"; },
() -> { Thread.sleep(50); return "Fast"; },
() -> { Thread.sleep(100); return "Medium"; }
);
ExecutorService pool = Executors.newFixedThreadPool(3);
String winner = pool.invokeAny(candidates);
System.out.println("First: " + winner); // Fast
pool.shutdown();execute() vs submit()
execute(Runnable) fires and forgets — no Future returned, exceptions are lost unless you add an uncaught exception handler. submit() wraps the task in a Future, capturing exceptions for retrieval via get().
ExecutorService pool = Executors.newFixedThreadPool(2);
// execute: exception silently lost
pool.execute(() -> { throw new RuntimeException("Oops!"); });
// submit: exception captured in Future
Future<?> f = pool.submit(() -> { throw new RuntimeException("Oops!"); });
try {
f.get();
} catch (ExecutionException e) {
System.out.println("Caught: " + e.getCause().getMessage());
}
pool.shutdown();Callable with Checked Exceptions
Unlike Runnable, Callable can throw checked exceptions — making it natural for I/O and database operations:
Callable<String> dbQuery = () -> {
// This compiles fine — checked exception is declared on Callable.call()
if (Math.random() < 0.5) throw new java.sql.SQLException("DB error");
return "row data";
};
Future<String> f = pool.submit(dbQuery);
try {
String data = f.get();
} catch (ExecutionException e) {
if (e.getCause() instanceof java.sql.SQLException) {
System.out.println("DB failed: " + e.getCause().getMessage());
}
}Parallelizing Independent Computations
Use multiple Callable submissions to run independent computations in parallel:
ExecutorService pool = Executors.newFixedThreadPool(4);
Future<Long> sumFuture = pool.submit(() -> LongStream.rangeClosed(1,1_000_000).sum());
Future<Long> prodFuture = pool.submit(() -> LongStream.rangeClosed(1,20).reduce(1L, (a,b)->a*b));
long sum = sumFuture.get();
long prod = prodFuture.get();
System.out.println("Sum: " + sum + ", 20!: " + prod);
pool.shutdown();Callable Result Aggregation
Submit many tasks and aggregate results once all are done:
int N = 8;
ExecutorService pool = Executors.newFixedThreadPool(N);
List<Future<Integer>> futures = new ArrayList<>();
for (int i = 0; i < N; i++) {
final int chunk = i;
futures.add(pool.submit(() -> chunk * chunk)); // i^2
}
int total = 0;
for (Future<Integer> f : futures) total += f.get();
System.out.println("Sum of squares: " + total);
pool.shutdown();Timeout on Callable
Pass a timeout to future.get(timeout, unit) to avoid waiting forever:
Future<String> f = pool.submit(() -> {
Thread.sleep(5000); // slow task
return "done";
});
try {
String result = f.get(1, TimeUnit.SECONDS);
} catch (TimeoutException e) {
System.out.println("Task timed out");
f.cancel(true); // interrupt the task
} catch (ExecutionException e) {
System.out.println("Task failed: " + e.getCause());
}Best Practices
Summary of best practices:
- Use
submit()overexecute()— exceptions are captured - Use Callable when you need a return value or checked exceptions
- Always call
future.get()with a timeout - Use invokeAll for batch processing; invokeAny for first-response-wins
- Always shut down the pool
Quick Check
What is the key advantage of Callable over Runnable?
Recap: Runnable vs Callable
Key takeaways:
- Runnable: void, no checked exceptions; submit returns Future> null
- Callable: returns V, can throw checked exceptions; submit returns Future
- invokeAll: submits batch, waits for all to finish
- invokeAny: returns first successful result, cancels rest
- Always use submit() for exception capture
Learn Java with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 104
- Lessons
- 374
Frequently Asked Questions
Is the “Submitting Tasks: Runnable vs Callable” lesson free?
Yes — the full text of “Submitting Tasks: Runnable vs Callable” is free to read here on the web. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO. The Java Academy course includes 4 lessons in total.
What will I learn in “Submitting Tasks: Runnable vs Callable”?
Submit Runnable and Callable tasks, and retrieve results via Future. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Java Academy?
No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 2 of 4.
How long does the “Submitting Tasks: Runnable vs Callable” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Java Academy lesson?
Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- ExecutorService and Thread Pool Types
- Submitting Tasks: Runnable vs Callable
- Future and Error Handling
- ScheduledExecutorService for Recurring Tasks