How to Capture Exceptions from Java ThreadPoolExecutor: submit vs execute
This article explains why tasks submitted with ExecutorService.submit silently swallow exceptions while execute prints them, and demonstrates three practical ways—try‑catch, Thread.setDefaultUncaughtExceptionHandler, and overriding afterExecute—to reliably retrieve exception information from a Java thread pool.
In real development we often use thread pools, but when a task throws an exception after being submitted, how can we handle it and retrieve the exception information? This article first shows a pseudo‑code example that demonstrates the different behaviours of submit and execute when an exception occurs.
public class ThreadPoolException {
public static void main(String[] args) {
// create a thread pool with a single thread
ExecutorService executorService = Executors.newFixedThreadPool(1);
// submit throws no immediate exception, other threads continue
executorService.submit(new task());
// execute throws the exception, other threads continue with new tasks
executorService.execute(new task());
}
}
// task class
class task implements Runnable {
@Override
public void run() {
System.out.println("Entered task method!!!");
int i = 1 / 0; // will cause ArithmeticException
}
}Running the code produces the following results:
We can see that execute prints the exception information, while submit does not. In production this is undesirable because we cannot guarantee that tasks never throw exceptions, and without the exception information we cannot make appropriate decisions.
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("Entered task method!!!");
int i = 1 / 0;
} catch (Exception e) {
System.out.println("Caught exception with try‑catch: " + e);
}
}
}The output shows that both submit and execute now clearly capture the exception.
Solution 2: Use Thread.setDefaultUncaughtExceptionHandler
Adding a try‑catch to every task can be cumbersome. Instead we can set a default uncaught‑exception handler for all threads created by the pool.
public class ThreadPoolException {
public static void main(String[] args) throws InterruptedException {
ThreadFactory factory = (Runnable r) -> {
Thread t = new Thread(r);
t.setDefaultUncaughtExceptionHandler((thread, e) -> {
System.out.println("Thread factory handler: " + e.getMessage());
});
return t;
};
ExecutorService executorService = new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(), factory);
executorService.submit(new task());
Thread.sleep(1000);
System.out.println("=== after 1s, execute ===");
executorService.execute(new task());
}
}
class task implements Runnable {
@Override
public void run() {
System.out.println("Entered task method!!!");
int i = 1 / 0;
}
}The handler catches exceptions from execute but not from submit, because submit wraps the task in a FutureTask that swallows the exception.
Solution 3: Override afterExecute
We can override ThreadPoolExecutor.afterExecute to process exceptions for tasks submitted via execute. For submit we need additional logic to extract the exception from the FutureTask.
public class ThreadPoolException3 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = new ThreadPoolExecutor(
2, 3, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
if (t != null) {
System.out.println("afterExecute caught execute exception: " + t.getMessage());
}
if (r instanceof FutureTask) {
try {
((Future<?>) r).get();
} catch (Exception e) {
System.out.println("afterExecute caught submit exception: " + e);
}
}
}
};
executorService.execute(new task3());
executorService.submit(new task3());
}
}
class task3 implements Runnable {
@Override
public void run() {
System.out.println("Entered task method!!!");
int i = 1 / 0;
}
}By checking whether the runnable is a FutureTask, we can also retrieve exceptions from tasks submitted with submit.
In summary, when you need to capture exceptions from a Java thread pool:
Use a try‑catch inside the task for simple cases.
Set a default UncaughtExceptionHandler via a custom thread factory for execute submissions.
Override afterExecute and, if the runnable is a FutureTask, call get() to obtain the exception for submit submissions.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.
