Handling ExecutionException - tips for Writing Robust Concurrent Code
Table of Contents
You Might Also Like
- What happens when the ExecutorService has no available threads
- Resource Tracking with Counters and Semaphores in Java
- How to use Java ThreadLocal with Thread Pools ‐ A Practical Use Case
- Speeding up JAXB with caching and pooling - with benchmarks
- Understanding Java Enum Constructors: Beyond Basic Constants
What is an ExecutionException? #
An ExecutionException is a checked exception in Java that occurs when a task executed by an ExecutorService fails. It is a wrapper around the original Exception or Error thrown by the task, providing a way to retrieve the underlying cause of the failure.
This exception is typically encountered when a FutureTask encounters an Exception during its execution that is not handled by the user code.
Example of ExecutionException #
Consider the following task that throws an exception:
package com.programmerpulse.executionexception;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ExecutionExceptionExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
throw new MyException("Checked Exception Occurred");
});
try {
future.get();
} catch (ExecutionException e) {
System.err.println("Caught ExecutionException: " + e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Task was interrupted");
} finally {
executor.shutdown();
}
}
}
Where MyException
is a custom exception:
package com.programmerpulse.executionexception;
public class MyException extends Exception {
public MyException(String message) {
super(message);
}
}
The output of this code will be:
Caught ExecutionException: java.util.concurrent.ExecutionException: com.programmerpulse.executionexception.MyException: Checked Exception Occurred
How to Handle ExecutionException #
To handle an ExecutionException, you can use the getCause()
method to retrieve the original exception that caused the failure. This allows you to understand the root cause of the problem and take appropriate action.
try {
String result = future.get(); // This will throw ExecutionException
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof MyException) {
System.out.println("Handling MyException: " + cause.getMessage());
// You can re-throw the exception if needed
} else if (cause instanceof MyRuntimeException) {
System.out.println("Caught MyRuntimeException: " + cause.getMessage());
} else if (cause instanceof Error) {
System.out.println("Caught Error: " + cause.getMessage());
} else {
System.out.println("Caught unexpected exception: " + cause);
}
} ... other catch blocks
This approach has the following advantages:
- Unwrapping Exceptions: The root cause of the exception is extracted, sp more specific handling can be applied.
- Selective Re-throwing: You can choose to re-throw specific exceptions while handling others gracefully.
- Best Practices: Aligns with best practices for exception handling by categorizing exceptions and providing meaningful error messages.
Always Handle Exceptions in the Task #
In general, you should not rely on the ExecutionException
to handle exceptions that occur in tasks submitted to an ExecutorService
. Instead, the task itself should handle its own exceptions. This is because:
- connectivity issues are handled and retried within the task
- resources are released
- better error messages are provided
However, in case of a coding bug in the task, handling the ExecutionException correctly is useful to still include as good practice.