Java CompletableFuture and Future examples with exception handling

nbodev
4 min readMar 1, 2022

1- Introduction

2- Versions

3- Code

4- Future

5- CompletableFuture

6- Example detailed

7- Interesting links

8- Conclusion

Introduction

This article is not a Future & CompletableFuture tutorial, there are many good ones already.

This is more an article in which I give a way to handle properly exceptions with an example related to the java Future and CompletableFuture (introduced in the java 8 version).

Versions

  • Java 11
  • Spring Boot 2.6.6

Code

Find the code here, maven is needed for the compilation:

Clone the project then run:

cd springboot-futures
mvn clean install

Then run:

java -jar ./target/futures-0.0.1-SNAPSHOT.jar

You can then trigger the examples by visiting those two examples in your browser:

http://localhost:8080/futures
http://localhost:8080/completable-futures

Future

In order to run asynchronous threads in java we can use the classic approach that involves an ExecutorCompletionService (more efficient than the ExecutorService) that returns a Future object which contains the result of the thread.

As you can see in my example class SampleFutures, we submit few tasks then we loop over the results, note that we fail one of the tasks deliberately by throwing an IllegalStateException.

The exception is caught when we loop over the finished tasks:

try {
final var taskResult = ecs.take().get();
logger.info("Result {}.", taskResult);
results.add(taskResult);
} catch (final Exception e) {
logger.error("Exception message has been raised {}.", e.getMessage());
}

This is the way I found to handle such exception since the ExecutorCompletionService does not not provide a way to handle that.

Note also that when we invoke the ecs.take().get() this is a blocking call, we need to wait for the result to become available. The Future does not notify us about the completion, no way to add a callback for instance that will be called and act on the result.

This is seen as a limitation of the java Future.

CompletableFuture

The CompletableFuture implements the Future interface but it offers more than the Future, it is non blocking, we can attach a callback to it, handles exception …

Check the SampleCompletableFuture class, as you can see below we use the handle(…) method where we have access to the result and the exception. This allows us to log properly the right message and the right exception that was re-thrown from the catch block of the supplyAsync method:

final var future = CompletableFuture
.supplyAsync(() -> {
try {
return processItem(item);
} catch (final Exception e) {
throw new CompletionException(e.getMessage(), e);
}
}, executor)
.handle((result, exception) -> {
if (exception != null) {
logger.error("Exception handle, message: {}.", exception.getMessage());
return null;
} else {
logger.info("No exception for item {}, processing time {}.", result.getItem(), result.getPrecessingTime());
return result;
}
});

Example detailed

For both examples we use a list of letters (items), the processing time of each item is random as it can take up to 5 seconds. This leads to a non respect of the submission order of the tasks.

If we are processing the letter “C” then we raise an IllegalStateException. We collect the results of the the threads and put them in a list List<TaskResult> that we return.

Below the output of the SampleCompletableFuture class:

22:12:04.117 [pool-1-thread-7] INFO com.nbodev.futures.sample.SampleCompletableFuture - Item G, sleepingTime 3614.
22:12:04.116 [pool-1-thread-4] INFO com.nbodev.futures.sample.SampleCompletableFuture - Item D, sleepingTime 2523.
22:12:04.116 [pool-1-thread-2] INFO com.nbodev.futures.sample.SampleCompletableFuture - Item B, sleepingTime 3479.
22:12:04.116 [pool-1-thread-6] INFO com.nbodev.futures.sample.SampleCompletableFuture - Item F, sleepingTime 1156.
22:12:04.116 [pool-1-thread-5] INFO com.nbodev.futures.sample.SampleCompletableFuture - Item E, sleepingTime 1805.
22:12:04.116 [pool-1-thread-3] INFO com.nbodev.futures.sample.SampleCompletableFuture - Item C, sleepingTime 3089.
22:12:04.116 [pool-1-thread-1] INFO com.nbodev.futures.sample.SampleCompletableFuture - Item A, sleepingTime 2414.
22:12:04.123 [pool-1-thread-3] ERROR com.nbodev.futures.sample.SampleCompletableFuture - Exception handle, message: Exception related to item C.
22:12:05.283 [pool-1-thread-6] INFO com.nbodev.futures.sample.SampleCompletableFuture - No exception for item F, processing time 1169.
22:12:05.930 [pool-1-thread-5] INFO com.nbodev.futures.sample.SampleCompletableFuture - No exception for item E, processing time 1815.
22:12:06.541 [pool-1-thread-1] INFO com.nbodev.futures.sample.SampleCompletableFuture - No exception for item A, processing time 2429.
22:12:06.648 [pool-1-thread-4] INFO com.nbodev.futures.sample.SampleCompletableFuture - No exception for item D, processing time 2534.
22:12:07.604 [pool-1-thread-2] INFO com.nbodev.futures.sample.SampleCompletableFuture - No exception for item B, processing time 3491.
22:12:07.739 [pool-1-thread-7] INFO com.nbodev.futures.sample.SampleCompletableFuture - No exception for item G, processing time 3625.

Interesting links

Conclusion

Hope this gives you an idea with a simple example how you can deal with the exceptions, there are many ways to deal with this use case, I choose the handle() method for my example feel free to visit the links section for more examples.

--

--