Handling exceptions from Java ExecutorService tasks
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Without any explicit exception handling logic, exceptions may go unnoticed, leading to silent failures.
Capturing Exceptions in Runnable Tasks
When submitting a Runnable task, exceptions thrown inside the task are not propagated out of the run method, meaning you need an alternate approach to capture them. One common method is to use the Future object returned by submitting a task.
In this example, calling future.get() forces the main thread to wait for task completion, and any exception thrown inside the task is wrapped in an ExecutionException.
Using Callable to Capture Exceptions
Unlike Runnable, the Callable interface allows tasks to return results and explicitly throw exceptions. The Future returned by ExecutorService's submit method can be used similarly to handle these exceptions.
This approach is advantageous because it directly integrates exception handling into the task execution flow.
Best Practices for Exception Handling
- Graceful Degradation: Design tasks such that they can fail gracefully. Consider retry mechanisms or fallback procedures.
- Logging: Ensure that all exceptions are logged with sufficient context for later analysis.
- Custom Thread Pools: Implement custom
ThreadPoolExecutorto override theafterExecute(Runnable r, Throwable t)method for centralized error handling.
Example: Custom Error Handling Using ThreadPoolExecutor
One way to handle exceptions globally is to extend ThreadPoolExecutor and override the afterExecute method:
Summary Table
| Approach | Description | Key Points |
Runnable and Future | Wrap tasks to capture exceptions using Future.get() method. | Non-blocking task submission. Retrieve exceptions after task completion. |
Callable and Future | Use Callable for tasks that might throw exceptions. Capture using Future.get(). | Returns results, supports exception handling natively. |
| Custom Executor | Extend ThreadPoolExecutor to handle post-execution exceptions globally. | Centralized handling for exceptions, configurable behavior. |
Conclusion
Handling exceptions in ExecutorService-submitted tasks requires thoughtful planning and understanding of concurrency principles. Utilizing Callable, Future, and custom thread pool implementations, developers can create robust applications that handle asynchronous task failures gracefully. Adopting best practices such as logging and graceful degradation ensures that your application remains resilient under adverse conditions.
Related reading
- Handling exceptions from Java ExecutorService tasks
- Handling interdependent and/or layered asynchronous calls
- Handling InterruptedException in Java
- Handling multiple returns asynchronously in node.js
- Handling InterruptedException in Java
- Has been compiled by a more recent version of the Java Runtime class file version 57.0
- Handling exceptions in Kafka streams
- Handling Mongoose validation errors – where and how?

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.