Personally, I disagree with all the answers here. The main issue with all of them is they provide the same solution in weird flavors. Instead, what you should be doing is creating your own thread factory that installs an uncaught exception handler on the thread that is being created. For example, this is the DefaultThreadFactory that is installed into any executor that would create threads on its own. Shamefully, it's still a private class as of Java 11, since I would like to extend it instead of copying it into my codebase. Below is a snippet how it appears in Executors.java file.
private static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
As you can see, the interface itself is a single method that handles creating new threads. There isn't much magic to it besides figuring out the thread group where is the thread factory created in. The interesting bit is that threads are created as non-daemon.
When the thread is created, you can call setThreadUncaughtExceptionHandler which accepts a handler where you should be handling any uncaught exceptions that had happened in that thread. By default, it will be inherited from your thread group, which has the following
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
By default, it will attempt to delegate handling to parent thread group if it exists, and only then test for platform default uncaught exception handler. Usually it is not explicitly installed. If you want to do some real damage to poor codebases that are not aware of this, you can install one via Thread#setDefaultUncaughtExceptionHandler. Don't worry, you won't get to do that if the runtime has Security manager in place.
If you were to install your own handler, that handler will be called instead of the group one.
Now with that out of the way, to your question: How do you handle exceptions in Executors. By default, a thread is considered dead if code is unable to handle its own errors. And I think you should adhere to that. Uncaught exception handler won't save your thread. Instead it will help you diagnose what happened. To segway into ScheduledExecutor implementations, which permit periodic execution of a runnable, the same rules apply: if one execution fails, the thread is killed, along with the runnable that was supposed to get run.
In short, handle your own errors. We have checked exceptions for a reason.
But what about unchecked exceptions?
Funny, since I will commit the same sin as other posters do: use try/catch on Throwable, but assert that it's not a ThreadDeath error. If you do get one, you must rethrow it to ensure the thread actually does die.