What is the Difference Between Runnable and Callable in Java With Examples?

What is the Difference Between Runnable and Callable in Java With Examples?

Java provides robust multithreading capabilities through the java.lang and java.util.concurrent packages. Two essential interfaces for creating tasks that can run concurrently are Runnable and Callable. While both are used to encapsulate tasks meant to run in a separate thread, they have key differences in how they are implemented and what they return.

1. Runnable Interface: Overview

The Runnable interface has been part of Java since version 1.0. It represents a task that can be executed by a thread. It does not return any result and cannot throw a checked exception.

public interface Runnable {
    void run();
}

Example of Runnable

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Task executed by: " + Thread.currentThread().getName());
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}
Key Characteristics of Runnable:
  • Does not return any result.
  • Cannot throw checked exceptions.
  • Used with Thread or ExecutorService.

2. Callable Interface: Overview

The Callable interface was introduced in Java 5 as part of the java.util.concurrent package. It is designed to return a result and may throw a checked exception.

public interface Callable<V> {
    V call() throws Exception;
}

Example of Callable

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

class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "Task executed by: " + Thread.currentThread().getName();
    }
}

public class CallableExample {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> future = executor.submit(new MyCallable());
        System.out.println(future.get()); // Waits and retrieves the result
        executor.shutdown();
    }
}
Key Characteristics of Callable:
  • Returns a result of a generic type.
  • Can throw checked exceptions.
  • Must be used with ExecutorService and Future.

3. Runnable vs Callable: Detailed Comparison

Feature Runnable Callable
Return Type void Generic type (e.g., String, Integer)
Throws Exceptions Cannot throw checked exceptions Can throw checked exceptions
Used With Thread or ExecutorService Only with ExecutorService
Available Since Java 1.0 Java 5

4. When Should You Use Runnable?

Use Runnable when:

  • You don’t need to return a result from the task.
  • Your code is simple and doesn’t throw checked exceptions.
  • You are using traditional threads or basic task execution.

Example: Using Runnable with ExecutorService

ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(() -> System.out.println("Runnable task running"));
executor.shutdown();

5. When Should You Use Callable?

Use Callable when:

  • You need the task to return a result.
  • You need to handle checked exceptions.
  • You are using concurrent task execution with ExecutorService.

Example: Callable with Multiple Threads

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;

public class MultipleCallableExample {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        List<Callable<String>> tasks = Arrays.asList(
            () -> "Task 1 result",
            () -> "Task 2 result",
            () -> "Task 3 result"
        );
        List<Future<String>> futures = executor.invokeAll(tasks);
        for (Future<String> future : futures) {
            System.out.println(future.get());
        }
        executor.shutdown();
    }
}

6. Handling Exceptions in Callable

The call() method in Callable allows us to throw exceptions, making it a better choice for tasks where error handling is important.

class ErrorProneCallable implements Callable<Integer> {
    public Integer call() throws Exception {
        throw new Exception("Simulated error");
    }
}

7. Using Lambda with Runnable and Callable

// Runnable with Lambda
Runnable r = () -> System.out.println("Runnable Lambda");

// Callable with Lambda
Callable<String> c = () -> "Callable Lambda";

8. Real-World Use Case: Runnable vs Callable

Example: Logging Service (Runnable)

Use Runnable when you’re just saving logs in a background thread, no return needed:

executor.execute(() -> logToFile("User logged in at " + LocalDateTime.now()));

Example: Data Fetching Service (Callable)

Use Callable when fetching something from a database:

Callable<User> fetchUser = () -> db.getUserById("123");
Future<User> userFuture = executor.submit(fetchUser);
User user = userFuture.get();

9. Conclusion: Runnable or Callable?

Please follow and like us:

Leave a Comment