Handling Exceptions in Java ThreadPool: submit vs execute and Custom Solutions
This article explains why exceptions submitted to a Java thread pool via submit are silent, how to retrieve them using Future.get(), compares submit and execute behaviors, and presents three practical solutions—including try‑catch, Thread.setDefaultUncaughtExceptionHandler, and overriding afterExecute—to reliably capture and process thread pool errors.
In real‑world 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? Before answering, we look at the thread‑pool source to understand the difference between submit and execute .
We first simulate an exception scenario with pseudo‑code:
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 throws exception, other threads continue with new task
executorService.execute(new task());
}
}
// Task class
class task implements Runnable {
@Override
public void run() {
System.out.println("Entered task method!!!");
int i = 1/0;
}
}Running the code shows that submit does not print the exception while execute does, which is undesirable in production because we cannot guarantee tasks never fail.
To obtain the exception from a submit call we must use Future.get() :
Future
submit = executorService.submit(new task());
submit.get();Result: Future.get() prints the exception.
Solution 1: Use try‑catch
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);
}
}
}Both submit and execute now clearly capture the exception.
Solution 2: Use Thread.setDefaultUncaughtExceptionHandler
Adding a global uncaught‑exception handler via a custom thread factory avoids sprinkling try‑catch blocks in every task:
public class ThreadPoolException {
public static void main(String[] args) throws InterruptedException {
ThreadFactory factory = (Runnable r) -> {
Thread t = new Thread(r);
t.setDefaultUncaughtExceptionHandler((Thread thread1, Throwable e) -> {
System.out.println("ThreadFactory 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("=== 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 custom 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
By overriding afterExecute in a custom ThreadPoolExecutor we can process exceptions for both execute and submit submissions:
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 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 task());
executorService.submit(new task());
}
}
class task implements Runnable {
@Override
public void run() {
System.out.println("Entered task method!!!");
int i = 1/0;
}
}The overridden afterExecute prints exception information for both submission methods.
In summary, when a task may throw an exception, using execute with a global handler or overriding afterExecute is preferable; if submit is required, retrieve the exception via Future.get() or handle it inside afterExecute .
Finally, the author encourages readers to like, share, and follow the public account for more technical content.
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.