1. Overview
Since Javaβs early days, multithreading has been a major aspect of the language. Runnable is the core interface provided for representing multithreaded tasks, and Java 1.5 provided Callable as an improved version of Runnable.
In this tutorial, 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 run by multiple threads. We can run Runnable tasks using the Thread class or ExecutorService, whereas we can only run Callables using the latter.
3. Return Values
Letβs look deeper at how these interfaces handle return values.
3.1. With Runnable
The Runnable interface is a functional interface and has a single run() method that doesnβt accept any parameters or return any values.
This works for situations where we arenβt looking for a result of the thread execution, such as 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.
We can launch the task 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 that returns a generic value V:
public interface Callable<V> {
V call() throws Exception;
}
Letβs 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, we donβt have a way to propagate further checked exceptions.
4.2. With Callable
Callableβs call() method contains the β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. We can check this 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 thrown since 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, 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 explored the differences between the Runnable and Callable interfaces.
The code backing this article is available on GitHub. Once you're
logged in as a Baeldung Pro Member, start learning and coding on the project.