Handling Exceptions in Java Thread Pools: submit vs execute and Custom afterExecute
This article explains why tasks submitted to a Java thread pool via submit do not print exceptions, how to retrieve those exceptions using Future.get, and presents three practical solutions—including try‑catch, Thread.setDefaultUncaughtExceptionHandler, and overriding afterExecute—to reliably capture and process thread‑pool errors.
1. Simulating Thread‑Pool Exceptions
In real projects we often use a thread pool, but when a task throws an exception we need to know how to capture it. First we simulate the situation with the following code:
public class ThreadPoolException {
public static void main(String[] args) {
// create a thread pool with a single thread
ExecutorService executorService = Executors.newFixedThreadPool(1);
// submit does not show the 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 program shows that execute prints the exception while submit silently ignores it.
To obtain the exception from a submit call you must call Future.get() on the returned Future:
Future<?> submit = executorService.submit(new task());
submit.get(); // prints the exception2. How to Get and Process the Exception
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);
}
}
}Both submit and execute now show the exception because the task itself handles it.
Solution 2: Use Thread.setDefaultUncaughtExceptionHandler
Adding a global uncaught‑exception handler to every thread created by the pool avoids having to write try‑catch in each task:
ThreadFactory factory = (Runnable r) -> {
Thread t = new Thread(r);
t.setDefaultUncaughtExceptionHandler((Thread thread, Throwable e) -> {
System.out.println("Thread factory handler: " + e.getMessage());
});
return t;
};
ExecutorService executorService = new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(10), factory);
executorService.submit(new task()); // no output – submit swallows the exception
Thread.sleep(1000);
System.out.println("=== after 1s, execute ===");
executorService.execute(new task()); // handler prints the exceptionThe handler receives exceptions from execute but not from submit because submit wraps the task in a FutureTask that catches the exception internally.
Solution 3: Override afterExecute in a custom ThreadPoolExecutor
By overriding afterExecute(Runnable r, Throwable t) we can process exceptions for both submission methods. For submit we need to check whether the runnable is a FutureTask and call get() to retrieve the stored exception.
ExecutorService executorService = new ThreadPoolExecutor(2, 3, 0L, 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());Running this shows that afterExecute can handle exceptions from both execute and submit submissions.
Key Takeaways
When a task throws an exception, execute propagates it to the thread’s uncaught‑exception mechanism, while submit captures it inside a FutureTask.
To see the exception from submit, call Future.get() or override afterExecute and inspect the FutureTask.
Three practical ways to handle exceptions: embed try‑catch in the task, set a global UncaughtExceptionHandler via a custom thread factory, or override afterExecute in a custom ThreadPoolExecutor.
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.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.
