Difference Between Callable and Runnable

In this Java concurrency tutorial, we will learn about Runnable and Callable Interfaces with practical examples. We will also learn a few main differences between the two interfaces and how to pick one between both in a multithreaded application.

1. Introduction

1.1. Runnable Interface

Runnable is a core interface and we can execute its implementing instances as a Thread or submit to ExecutorService. This interface contains only one abstract method run(), which we must override to define the Thread’s job. Since Java 8, Runnable is a functional interface.

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

1.2. Callable Interface

Callable is also one of the core interfaces and they can only be executed via ExecutorService and not by the traditional Thread class. It contains one abstract method call() which should contain the business logic to be executed by the ExecutorService. Callable is also a functional interface.

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

2. Differences between Runnable and Callable

Let us now look at some of the main differences between the two interfaces:

2.1. Methods to Override

In order to use Runnable interface, we need to override the run() method.

class CallableTask implements Callable<String>{

  public String call() throws Exception{
      return "Returning from callable";
  } 
}  

To use the Callable interface we need to override the call() method.

class RunnableTask implements Runnable {

  public void run() {
      System.out.println("Thread executed !");
  }
}

2.2. Execution Mechanism

Both Callable and Runnable interface are used to encapsulate the tasks which are to be executed by another thread.

Runnable instances can be run by Thread class and ExecutorService, both.

RunnableTask task = new RunnableTask();
Thread thread = new Thread(task);
thread.start();

Callable instances can only be executed via ExecutorService. No constructor defined in the Thread class accepts a Callable instance.

ExecutorService executor = Executors.newFixedThreadPool(2);

CallableTask task = new CallableTask();
Future<String> future = executor.submit(task);

2.3. Return Types

In Runnable, the return type of run() is void, so this method cannot return any value.

public void run();

In Callable, the call() method returns the Future object that provides methods to get the result of the computation and to check if the task has been completed or cancelled.

public Object call() throws Exception;

2.4. Checked Exceptions

In Runnable, the run() method cannot throw checked exceptions, so we can’t have a way to propagate the checked exceptions. We have to handle the checked exceptions inside run() using try/catch block only.

In the following example, the FileInputStream constructor throws the checked exception FileNotFoundException. We must handle the exception in the catch block else the code does not compile.

class FileReaderTask implements Runnable {

  public void run(){

    try(FileInputStream fis = new FileInputStream("file-path")){
      //read file
    } catch (FileNotFoundException e) {
      //handle exception
    } catch (IOException e) {
      //handle exception
    }
  }
}

In Callable, the call() method can throw the checked exceptions that we can easily propagate. In the following example, we can let the call() method rethrow the exception to the caller method to handle it.

class FileReaderTask implements Callable {

  public Object call() throws IOException {

    try(FileInputStream fis = new FileInputStream("file-path")){
      //read file
    }
    return null;
  }
}

On the caller method side, ExecutionException is thrown when we invoke the Future.get() method. If we do not invoke the get() method, the exception will be lost, and the thread will be marked completed.

Future<Integer> future = executorService.submit(callableTask); //an exception is thrown from callable task

future.isDone();   //true - no exception reported

future.get()...;  //throws ExecutionException

3. When to Use

Both Runnable and Callable interfaces have their uses, and the Executor framework in java supports both. Runnable has been there for a long, but it is still in use and is a core interface for designing concurrent applications.

  • Runnable does not return any value, so we can use it for fire-and-forget calls, especially when we are not interested in the result of the task execution.
  • Callable can throw exceptions and return values, so they are better for result-bearing tasks (such as fetching a resource from the network, performing an expensive computation to get some value, etc.).

4. Conclusion

We have learned about Java Runnable and Callable Interfaces with examples. We have also seen some of the main differences between the two and how to decide which one to choose while working in a multithreaded environment.

Happy Learning !!

Comments

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode