Search⌘ K
AI Features

CompletionException

Explore the CompletionException in Java concurrency, understand how it differs from ExecutionException, and learn to handle asynchronous task errors using CompletableFuture methods like join and getNow.

We'll cover the following...

If you are interviewing, consider buying our number#1 course for Java Multithreading Interviews.

Overview

The CompletionException extends from the RuntimeException and is thrown when a task hits an error or exception when executing.

The CompletionException and the ExecutionException might seem similar but there’s a fundamental difference between the two. The ExecutionException is thrown only when an attempt is made to retrieve the result/outcome of a task, while the CompletionException can be thrown when waiting on a task to complete or when trying to retrieve a result computed by a task. In a sense CompletionException can be thought of as broader in scope than ExecutionException.

Example

We’ll use the CompletableFuture class to demonstrate the CompletionException. The CompletableFuture class is used to program asynchronously in Java. It exposes a method completeExceptionally that takes in an instance of Throwable to indicate the failure experienced by the task. When the main thread attempts to wait for the task to complete by invoking the join() method the CompletionException is thrown.

Java
import java.util.concurrent.*;
class Demonstration {
public static void main( String args[] ) {
// create an executor service and a completable future
ExecutorService executorService = Executors.newFixedThreadPool(5);
CompletableFuture<Integer> completableFuture = new CompletableFuture<>();
try {
// create a command that marks the CompletableFuture instance complete exceptionally
Runnable command = new Runnable() {
@Override
public void run() {
completableFuture.completeExceptionally(new Exception("Something bad happened."));
}
};
// submit the command
executorService.submit(command);
// wait for the future to complete. This statement throws the CompletionException
Thread.sleep(500);
completableFuture.join();
} catch (CompletionException ce) {
System.out.println("CompletionException has been thrown." + ce.getCause());
} catch (InterruptedException ie) {
// ignore for now
} finally {
// remember to shutdown the executor service in a finally block
executorService.shutdown();
}
}
}

Note that in the above program, if we attempt to retrieve the result of the task, which should be an integer we’ll get ExecutionException instead of the CompletionException.

Java
import java.util.concurrent.*;
class Demonstration {
public static void main( String args[] ) {
// create an executor service and a completable future
ExecutorService executorService = Executors.newFixedThreadPool(5);
CompletableFuture<Integer> completableFuture = new CompletableFuture<>();
try {
// create a command that marks the CompletableFuture instance complete exceptionally
Runnable command = new Runnable() {
@Override
public void run() {
completableFuture.completeExceptionally(new Exception("Something bad happened."));
}
};
// submit the command
executorService.submit(command);
// wait for the future to complete. This statement throws the CompletionException
Thread.sleep(500);
completableFuture.get();
} catch (CompletionException ce) {
System.out.println("CompletionException has been thrown." + ce.getCause());
} catch (ExecutionException ee) {
System.out.println("Execution Exception has been thrown " + ee.getCause());
} catch (InterruptedException ie) {
// ignore for now
} finally {
// remember to shutdown the executor service in a finally block
executorService.shutdown();
}
}
}

The CompletableFuture also offers another method getNow() that is non-blocking unlike the get() method. The getNow() method either returns the computed value if the future has completed or a default value that is passed-in at the time of the invocation of the method. The getNow() method throws the CompletionExecution instead of the ExecutionException in case the future is exceptionally completed as shown in the following program.

Java
import java.util.concurrent.*;
class Demonstration {
public static void main( String args[] ) {
// create an executor service and a completable future
ExecutorService executorService = Executors.newFixedThreadPool(5);
CompletableFuture<Integer> completableFuture = new CompletableFuture<>();
try {
// create a command that marks the CompletableFuture instance complete exceptionally
Runnable command = new Runnable() {
@Override
public void run() {
completableFuture.completeExceptionally(new Exception("Something bad happened."));
}
};
// submit the command
executorService.submit(command);
// wait for the future to complete. This statement throws the CompletionException
Thread.sleep(500);
completableFuture.getNow(-1);
} catch (CompletionException ce) {
System.out.println("CompletionException has been thrown." + ce.getCause());
} catch (InterruptedException ie) {
// ignore for now
} finally {
// remember to shutdown the executor service in a finally block
executorService.shutdown();
}
}
}

Interestingly, if the CompletableFuture completes successfully but then throws an exception, the join() and get() methods don’t throw any exception in the main thread as demonstrated by the following program.

Java
import java.util.concurrent.*;
class Demonstration {
public static void main( String args[] ) {
// create an executor service and a completable future
ExecutorService executorService = Executors.newFixedThreadPool(5);
CompletableFuture<Integer> completableFuture = new CompletableFuture<>();
try {
// create a command that marks the CompletableFuture instance complete exceptionally
Runnable command = new Runnable() {
@Override
public void run() {
// complete the future successfully
completableFuture.complete(786);
// throw an exception now
throw new RuntimeException("Something bad happened.");
}
};
// submit the command
executorService.submit(command);
// wait for the future to complete. This statement throws the CompletionException
Thread.sleep(500);
completableFuture.get(); // or completableFuture.join()
} catch (ExecutionException ce) {
System.out.println("ExecutionException has been thrown." + ce.getCause());
} catch (InterruptedException ie) {
// ignore for now
} finally {
// remember to shutdown the executor service in a finally block
executorService.shutdown();
}
}
}