How to Capture Exceptions from Java ThreadPool Tasks: submit vs execute and Three Solutions
This article explains why exceptions from tasks submitted to a Java ThreadPool using submit are not printed, how execute shows them, and presents three practical approaches—try‑catch within the task, a custom Thread.setDefaultUncaughtExceptionHandler, and overriding afterExecute—to reliably obtain and handle those exceptions.
In real‑world development a thread pool is frequently used, but when a task throws an exception the handling differs between submit and execute . The article first shows a simple pseudo‑code example that creates a fixed thread pool, submits a task that divides by zero, and observes that submit produces no console output while execute prints the exception.
public class ThreadPoolException {
public static void main(String[] args) {
// create a thread pool
ExecutorService executorService = Executors.newFixedThreadPool(1);
// submit does not show exception, other threads continue
executorService.submit(new task());
// execute shows exception, other threads continue with new task
executorService.execute(new task());
}
}
class task implements Runnable {
@Override
public void run() {
System.out.println("进入了task方法!!!");
int i = 1 / 0;
}
}The result demonstrates that submit silently swallows the exception. To retrieve the exception you must call Future.get() on the returned Future :
// submit returns a Future; calling get() prints the exception
Future
submit = executorService.submit(new task());
submit.get();Three practical solutions are then presented.
Solution 1: Use try‑catch inside the task
public class ThreadPoolException {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.submit(new task());
executorService.execute(new task());
}
}
class task implements Runnable {
@Override
public void run() {
try {
System.out.println("进入了task方法!!!");
int i = 1 / 0;
} catch (Exception e) {
System.out.println("使用了try‑catch捕获异常" + e);
}
}
}Both submit and execute now clearly output the caught exception.
Solution 2: Set a default UncaughtExceptionHandler via a custom ThreadFactory
public class ThreadPoolException {
public static void main(String[] args) throws InterruptedException {
ThreadFactory factory = (Runnable r) -> {
Thread t = new Thread(r);
t.setDefaultUncaughtExceptionHandler((Thread thread, Throwable e) -> {
System.out.println("线程工厂设置的exceptionHandler" + e.getMessage());
});
return t;
};
ExecutorService executorService = new ThreadPoolExecutor(
1, 1, 0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(10), factory);
executorService.submit(new task());
Thread.sleep(1000);
System.out.println("==================为检验打印结果,1秒后执行execute方法");
executorService.execute(new task());
}
}
class task implements Runnable {
@Override
public void run() {
System.out.println("进入了task方法!!!");
int i = 1 / 0;
}
}The custom factory’s UncaughtExceptionHandler catches exceptions from execute , while submit still requires Future.get() to surface the error.
Solution 3: Override afterExecute in a custom ThreadPoolExecutor
public class ThreadPoolException3 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = new ThreadPoolExecutor(
2, 3, 0, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(10)) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
if (t != null) {
System.out.println("afterExecute捕获execute异常: " + t.getMessage());
}
if (r instanceof FutureTask) {
try {
((Future
) r).get();
} catch (Exception e) {
System.out.println("afterExecute捕获submit异常: " + e);
}
}
}
};
executorService.execute(new task());
executorService.submit(new task());
}
}
class task implements Runnable {
@Override
public void run() {
System.out.println("进入了task方法!!!");
int i = 1 / 0;
}
}This approach handles exceptions from both execute (via the Throwable t parameter) and submit (by detecting a FutureTask and invoking get() ).
Underlying the behavior, submit wraps the task in a FutureTask whose run() method catches all throwables, stores them in an internal outcome field, and later re‑throws them as an ExecutionException when Future.get() is called. In contrast, execute lets the exception propagate to the thread’s UncaughtExceptionHandler or to an overridden afterExecute method.
Therefore, when you need exception visibility without manually calling get() , prefer execute with a proper handler; otherwise, use submit together with Future.get() or one of the three illustrated techniques.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.