Runnable vs. Callable in Java

1. Overview

Since the early days of Java, multithreading has been a major aspect of the language. Runnable is the core interface provided for representing multi-threaded tasks and Callable is an improved version of Runnable that was added in Java 1.5.

In this article, we’ll explore the differences and the applications of both interfaces.

2. Execution Mechanism

Both interfaces are designed to represent a task that can be executed by multiple threads. Runnable tasks can be run using the Thread class or ExecutorService whereas Callables can be run only using the latter.

3. Return Values

Let’s have a deeper look at the way these interfaces handle return values.

3.1. With Runnable

The Runnable interface is a functional interface and has a single run() method which doesn’t accept any parameters and does not return any values.

This is suitable for situations where we are not looking for a result of the thread execution, for example, incoming events logging:

public interface Runnable {
    public void run();
}

Let’s understand this with an example:

public class EventLoggingTask implements  Runnable{
    private Logger logger
      = LoggerFactory.getLogger(EventLoggingTask.class);

    @Override
    public void run() {
        logger.info("Message");
    }
}

In this example, the thread will just read a message from the queue and log it in a log file. There’s no value returned from the task; the task can be launched using ExecutorService:

public void executeTask() {
    executorService = Executors.newSingleThreadExecutor();
    Future future = executorService.submit(new EventLoggingTask());
    executorService.shutdown();
}

In this case, the Future object will not hold any value.

3.2. With Callable

The Callable interface is a generic interface containing a single call() method – which returns a generic value V:

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

Let’s have a look at calculating the factorial of a number:

public class FactorialTask implements Callable<Integer> {
    int number;

    // standard constructors

    public Integer call() throws InvalidParamaterException {
        int fact = 1;
        // ...
        for(int count = number; count > 1; count--) {
            fact = fact * count;
        }

        return fact;
    }
}

The result of call() method is returned within a Future object:

@Test
public void whenTaskSubmitted_ThenFutureResultObtained(){
    FactorialTask task = new FactorialTask(5);
    Future<Integer> future = executorService.submit(task);
 
    assertEquals(120, future.get().intValue());
}

4. Exception Handling

Let’s see how suitable they are for exception handling.

4.1. With Runnable

Since the method signature does not have the “throws” clause specified,there is no way to propagate further checked exceptions.

4.2. With Callable

Callable’s call() method contains “throws Exception” clause so we can easily propagate checked exceptions further:

public class FactorialTask implements Callable<Integer> {
    // ...
    public Integer call() throws InvalidParamaterException {

        if(number < 0) {
            throw new InvalidParamaterException("Number should be positive");
        }
    // ...
    }
}

In case of running a Callable using an ExecutorService, the exceptions are collected in the Future object, which can be checked by making a call to the Future.get() method. This will throw an ExecutionException – which wraps the original exception:

@Test(expected = ExecutionException.class)
public void whenException_ThenCallableThrowsIt() {
 
    FactorialCallableTask task = new FactorialCallableTask(-5);
    Future<Integer> future = executorService.submit(task);
    Integer result = future.get().intValue();
}

In the above test, the ExecutionException is being thrown as we are passing an invalid number. We can call the getCause() method on this exception object to get the original checked exception.

If we don’t make the call to the get() method of Future class – then the exception thrown by call() method will not be reported back, and the task will still be marked as completed:

@Test
public void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){
    FactorialCallableTask task = new FactorialCallableTask(-5);
    Future<Integer> future = executorService.submit(task);
 
    assertEquals(false, future.isDone());
}

The above test will pass successfully even though we’ve thrown an exception for the negative values of the parameter to FactorialCallableTask.

5. Conclusion

In this article, we’ve explored the differences between the Runnable and Callable interfaces.

As always, the complete code for this article is available over on GitHub.

Related posts:

Java Program to Check whether Graph is Biconnected
Lập trình đa luồng với Callable và Future trong Java
Fixing 401s with CORS Preflights and Spring Security
Java Program to Implement CopyOnWriteArraySet API
HttpClient 4 Cookbook
Guide to java.util.concurrent.Future
Debugging Reactive Streams in Java
Use Liquibase to Safely Evolve Your Database Schema
Java Program to Find the Minimum value of Binary Search Tree
Java Program to Represent Graph Using 2D Arrays
Java Program to Implement Hash Tables Chaining with Doubly Linked Lists
Java Program to Implement Heap’s Algorithm for Permutation of N Numbers
OAuth2.0 and Dynamic Client Registration
Java Program to Implement AVL Tree
Java Program to Perform Preorder Non-Recursive Traversal of a Given Binary Tree
Java Program to Solve a Matching Problem for a Given Specific Case
Chương trình Java đầu tiên
Java Program to Check the Connectivity of Graph Using BFS
Converting a Stack Trace to a String in Java
Tìm hiểu cơ chế Lazy Evaluation của Stream trong Java 8
Java Program to Implement ConcurrentHashMap API
@Before vs @BeforeClass vs @BeforeEach vs @BeforeAll
Java Program to Implement Hash Tables with Linear Probing
Java Program to Generate All Subsets of a Given Set in the Gray Code Order
Java Program to Implement the Hungarian Algorithm for Bipartite Matching
Zipping Collections in Java
Spring Boot - Servlet Filter
Java Program to Check whether Directed Graph is Connected using BFS
Quản lý bộ nhớ trong Java với Heap Space vs Stack
Introduction to Spring Cloud Rest Client with Netflix Ribbon
Java Program to Implement Double Order Traversal of a Binary Tree
Java Program to Implement Hash Tables Chaining with List Heads