Runnable & Callable

About

Runnable and Callable are two interfaces in Java used for executing tasks in separate threads. Both are commonly used in multithreading and concurrency but have differences in functionality.

Runnable Interface

Definition

  • Runnable is an interface that represents a task to be executed by a thread.

  • It has one method:

    void run();
  • It does not return a result and cannot throw checked exceptions.

Since Java 8, Runnable can be used with lambdas:

Runnable runnable = () -> System.out.println("Runnable task executed.");

Usage

A Runnable task can be executed by:

  1. Creating a Thread object

  2. Using ExecutorService

Example 1: Implementing Runnable using a Class

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Executing task in thread: " + Thread.currentThread().getName());
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable()); // Assign task to thread
        thread.start(); // Starts a new thread
    }
}

Here, the run() method is executed in a separate thread.

Example 2: Using Runnable with ExecutorService

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class RunnableWithExecutor {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        executor.submit(new MyRunnable());
        executor.shutdown();
    }
}

ExecutorService is a thread pool manager that efficiently manages threads.

Callable Interface

Definition

  • Callable<T> is a functional interface introduced in Java 5.

  • Unlike Runnable, it returns a result and can throw checked exceptions.

  • It has one method:

    T call() throws Exception;
  • The return type T allows it to be used for asynchronous computation.

Since Java 8, Callable can be used with lambdas:

Callable<String> callable = () -> "Callable task executed.";

Usage

A Callable task is executed using an ExecutorService and returns a Future<T> object.

Example 1: Implementing Callable with ExecutorService

import java.util.concurrent.Callable;

class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        Thread.sleep(2000); // Simulate delay
        return "Task completed by " + Thread.currentThread().getName();
    }
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableExample {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> future = executor.submit(new MyCallable()); // Submit task

        System.out.println("Waiting for result...");
        String result = future.get(); // Blocks until the result is available
        System.out.println("Result: " + result);

        executor.shutdown();
    }
}

Combining Runnable and Callable

If we want a Runnable but need a result, use Executors.callable(). This is useful when converting a Runnable to Callable.

import java.util.concurrent.Callable;
import java.util.concurrent.Executors;

public class RunnableToCallable {
    public static void main(String[] args) {
        Callable<Object> callableTask = Executors.callable(() -> 
            System.out.println("Runnable inside Callable"));
    }
}

Using FutureTask (Runnable + Callable)

If we need both Runnable and Callable behaviors, use FutureTask<T>.FutureTask allows combining Runnable execution with Callable result handling.

import java.util.concurrent.FutureTask;

public class FutureTaskExample {
    public static void main(String[] args) throws Exception {
        FutureTask<String> futureTask = new FutureTask<>(() -> "Task completed!");
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println("Result: " + futureTask.get());
    }
}

Comparison Runnable and Callable

Feature
Runnable
Callable

Introduced in

Java 1.0

Java 5

Method

void run()

T call() throws Exception

Return Value

No (void)

Yes (Generic T)

Exception Handling

Cannot throw checked exceptions

Can throw checked exceptions

Used With

Thread, ExecutorService

ExecutorService, Future

Best For

Executing tasks without result

Executing tasks that return a value

When to Use Runnable vs Callable vs Others

Use Case

Use

Why?

You need to execute a task without returning a result

Runnable

Runnable.run() has a void return type, making it ideal for simple background tasks.

You need to execute a task and return a result

Callable

Callable.call() returns a value, allowing you to capture the task's output.

You need to execute a task but may need to check its completion later

Callable + Future

Future<T> allows retrieving the result later without blocking the main thread.

You need to execute a task and get notified when it's completed

FutureTask

FutureTask allows combining Runnable and Callable, and it can be used as a Future.

You need to execute multiple independent tasks and wait for all to complete

ExecutorService + invokeAll()

invokeAll() submits multiple Callabletasks and waits for all results.

You need to execute multiple independent tasks and get the first completed result

ExecutorService + invokeAny()

invokeAny() submits multiple Callabletasks and returns the first successful result.

You need fine-grained control over thread execution (e.g., priority, interruption)

Thread + Runnable

Thread can directly manage execution but is less flexible than thread pools.

You need to handle checked exceptions in a background task

Callable

Callable.call() supports throwing checked exceptions.

You need to execute a task repeatedly at a fixed rate

ScheduledExecutorService

scheduleAtFixedRate() and scheduleWithFixedDelay() allow scheduled execution.

You need to execute CPU-intensive parallel tasks

ForkJoinPool

ForkJoinPool supports work-stealing, ideal for recursive and parallel tasks.

You need to execute stream operations in parallel

Parallel Streams

stream().parallel() splits work across multiple cores automatically.

You need to process a collection of tasks asynchronously

CompletableFuture

CompletableFuture allows non-blocking execution and chaining tasks together.

You need to execute a task with a timeout

ExecutorService + Future.get(timeout)

Future.get(timeout, TimeUnit.SECONDS)prevents indefinite waiting.

You need to execute a task in the background and monitor progress

CompletableFuture + thenApply/thenAccept

CompletableFuture allows progress tracking and callbacks.

You need non-blocking, event-driven async execution

CompletableFuture + SupplyAsync()

Asynchronous execution without blocking threads.

Last updated

Was this helpful?