0Pricing
Java Academy · Lesson

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 — 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, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

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() over execute() — 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

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, and the Java Academy course includes 4 lessons in total. 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.

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; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

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

  1. ExecutorService and Thread Pool Types
  2. Submitting Tasks: Runnable vs Callable
  3. Future and Error Handling
  4. ScheduledExecutorService for Recurring Tasks
← Back to Java Academy