An Adventure in Java Concurrency — Callable vs Runnable Explained

Deen Aariff
4 min readAug 14, 2017

--

I recently began focusing on Java as my core programming language in an attempt to solidify my backend development skills and immerse myself in the world of Object Oriented Programming. My latest personal project has been a implementation of the Raft Consensus Algorithm, in which I utilize concurrency in Java along with socket programming.

While working on my implementation of Raft, I wanted the code that I ran in a separate thread of execution to be able to throw exceptions. Seeing that this is not possible in the Runnable interface, I had to turn my head towards another interface in Java: Callable.

Callable is an Java (5) interface that can be run in a separate thread of execution and is capable of returning a value as well as throwing an exception.

I found the differences between Runnable and Callable to be interesting. Therefore, I wanted to write about what I learned regarding the topic in hopes that someone exploring the same concept in the future can make use of this post.

Let’s Talk About Runnable

Runnable is an interface that requires you to override the abstract method public void run(). An implementation of the Runnable interface would look like the following.

package com.concurrency.runnable;import java.util.concurrent.*;public class RunnableExample implements Runnable {  @Override
public void run() {
System.out.println("Hello World");
}
}

If we want to run our class in a separate thread of execution we can use the Thread class. In the code below, a Thread object is created by passing a Runnable object as the parameter.

package com.concurrency.examples;import java.lang.Thread;
import com.concurrency.runnable.*;
public class ThreadExample { public static void main(String[] args) {
Thread thread = new Thread(new RunnableExample());
thread.start();
}
}

By calling the method start() of our Thread object, we can run the code in the public void run() method of our Runnable Example class as a separate process.

Note that we can still handle exceptions using Runnable, we just need to handle all exceptions within the run() method that we override.

How does this compare to Callable?

As a reminder, Callable, like Runnable, is a Java interface that can be run in a separate thread of execution. However, Callable can be used to return data from and throw exceptions from the code that is run in these computations.

Just as Runnable requires us to override the public void run() method, Callable requires us to override the public <?> call() method. The return type of the method is determined by type we specify in the generic interface of the Callable class.

package com.concurrency.callable;import java.util.concurrent.Callable;public class Summation implements Callable<Integer> {  private int a;
private int b;
public Summation(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public Integer call() {
return this.a+this.b;
}
}

We can run Callable in separate thread and obtain the result via a Future Object. The Future class graciously provides us methods which can be called to handle the result of our computation.

package com.concurrency.examples;import com.concurrency.callable.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Callable;
public class FutureExample { public static void main(String[] args) { int a = 1;
int b = 2;
Callable<Integer> callable = new Summation(a,b); ExecutorService exec = Executors.newFixedThreadPool(3);
Future<Integer> future = exec.submit(callable);
try { // will print out 1 + 2 = 3
int result = future.get();
System.out.println(a + " + " + b + " = " + result);
} catch (Exception e) {
System.out.println("The error is: " + e);
}
}
}

In this fashion, we can use Callable and Future to run our code in a separate thread of execution and utilize the returned data in original context of our code.

Utilizing Callable to Throw Exceptions

One of the benefits of the Callable interface is the fact that it enables us to throw exceptions that are handled outside of the separate thread of execution.

For example, suppose we alter our implementation of Callable to force an exception in our call() method.

package com.concurrency.callable;import java.util.concurrent.Callable;public class SummationException implements Callable<Integer> {  private boolean throwException;  public SummationException(boolean throwException) {
this.throwException = throwException;
}
@Override
public Integer call() throws Exception {
if(this.throwException == true) {
throw new Exception();
}
return 0;
}
}

This will result in an exception being thrown by future.get, which can be handled in the try catch statement surrounding the method call.

package com.concurrency.examples;import com.concurrency.callable.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Callable;
public class FutureExample { public static void main(String[] args) { Callable<Integer> callable = new SummationException(true); ExecutorService exec = Executors.newFixedThreadPool(3);
Future<Integer> future = exec.submit(callable);
try {
int result = future.get();
System.out.println(result);
} catch (Exception e) {
// The exception will be printed out
System.out.println("Exception: " + e);
}
}
}

Conclusion

Thanks for reading my post! I hope I was able to communicate the differences between the Callable and Runnable interfaces and their use cases.

I would love to hear from you in the comments! If you notice any errors or incorrect information in the post, please let me know and I’ll be sure to make the appropriate correction.

--

--

Deen Aariff

CS/Math Student. I love prototyping technologies and software tools.