ExecutorService is the central mechanism to execute tasks in Java. When we run our tasks in a thread pool backed by an ExecutorService, we must pay attention to exception handling. Remember that Java doesn’t require a method to handle or declare an unchecked RuntimeException, thus any code can throw a RuntimeException without us knowing. Upon getting an exception, we can log the error, notify a system, or take other diagnostic actions. In this tutorial, we’ll examine how we can handle exceptions thrown from the tasks running in an ExecutorService.
Handle with UncaughtExceptionHandler
Next, we’ll register an UncaughtExceptionHandler to the worker threads. Remember that ExecutorService implementations use a ThreadFactory to create a new worker thread. For our purposes, we’ll create a new ThreadFactory implementation that sets an UncaughtExceptionHandler.
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class WithUncaughtExceptionHandler {
public static class AppThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler(new AppExceptionHandler());
return thread;
}
}
// Interface for handlers invoked when a Thread abruptly terminates due to an uncaught exception
public static class AppExceptionHandler implements UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("Uncaught Exception occurred on thread: " + t.getName());
System.out.println("Exception message: " + e.getMessage());
}
}
public void executeThenThrowUnchecked() {
ExecutorService service = Executors.newFixedThreadPool(1, new AppThreadFactory());
service.execute(() -> {
System.out.println("I will throw RuntimeException now.");
throw new RuntimeException("Planned exception after execute()");
});
service.shutdown();
}
public static void main(String[] args) {
WithUncaughtExceptionHandler handler = new WithUncaughtExceptionHandler();
handler.executeThenThrowUnchecked();
}
}
Output —
I will throw RuntimeException now.
Uncaught Exception occurred on thread: Thread-0
Exception message: Planned exception after execute()
— — — — — — — — — — — — — — — — — — — — — -
Handle with Wrapper Task
We’ll now investigate how we can handle an uncaught exception wrapping the original task. The previous UncaughtExceptionHandler approach applies to all threads and tasks in a thread pool. However, if we’re running different tasks in the same thread pool and they require different exception handling logic, this may not be optimal. Or we aren’t even allowed to set a handler because the task submission code is using a preconfigured pool. In these cases, we can wrap our original task in another Runnable or Callable. The wrapper class catches the exception and takes the appropriate action.
package com.javaByExample;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class WithWrappingTask {
public static class CatchingRunnable implements Runnable{
private final Runnable delegate;
public CatchingRunnable(Runnable runnable) {
this.delegate = runnable;
}
@Override
public void run() {
try {
delegate.run();
} catch (Exception e) {
System.out.println("Exception Occurred "+e.getMessage()); // Log, notify etc...
throw e;
}
}
}
public void executeThenThrowUnchecked() {
final ExecutorService executorService = Executors.newFixedThreadPool(1);
final CatchingRunnable catchingRunnable = new CatchingRunnable(() -> {
System.out.println("I will throw RuntimeException now.");
throw new RuntimeException("Planned exception after execute()");
});
executorService.execute(catchingRunnable);
executorService.shutdown();
}
public static void main(String[] args) {
WithWrappingTask task = new WithWrappingTask();
task.executeThenThrowUnchecked();
}
}
Output —
I will throw RuntimeException now.
Exception in thread "pool-1-thread-1" Exception Occurred Planned exception after execute()
java.lang.RuntimeException: Planned exception after execute()
at com.javaByExample.WithWrappingTask.lambda$0(WithWrappingTask.java:31)
at com.javaByExample.WithWrappingTask$CatchingRunnable.run(WithWrappingTask.java:18)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
at java.base/java.lang.Thread.run(Thread.java:832)