Handling Exceptions in Java ThreadPool: submit vs execute and Custom Solutions
This article explains why tasks submitted to a Java ThreadPool via submit do not print exceptions, how to retrieve them using Future.get(), and presents three practical solutions—including try‑catch, Thread.setDefaultUncaughtExceptionHandler, and overriding afterExecute—to reliably capture and process thread pool exceptions.
In Java development, thread pools are frequently used, but when a task throws an exception, the handling differs between submit and execute. This article demonstrates the behavior, shows how to obtain exception information, and provides three concrete solutions.
Code example of a task that throws an exception:
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 print 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("Entering task method!!!");
int i = 1 / 0; // This will cause ArithmeticException
}
}Running the above code shows that execute prints the exception while submit silently discards it. In production, silently lost exceptions are unacceptable.
To retrieve the exception from a submit call, you must use the returned Future:
Future<?> submit = executorService.submit(new task());
submit.get(); // This will throw ExecutionException containing the original exceptionSolution 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("Entering task method!!!");
int i = 1 / 0;
} catch (Exception e) {
System.out.println("Caught exception with try‑catch: " + e);
}
}
}Both submit and execute now capture the exception via the explicit try‑catch block.
Solution 2: Use Thread.setDefaultUncaughtExceptionHandler
Instead of adding try‑catch to every task, you can set a global uncaught exception handler in a custom thread factory:
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<>(10), factory);
executorService.submit(new task()); // No output from handler
Thread.sleep(1000);
System.out.println("=== after 1s, executing ===");
executorService.execute(new task()); // Handler prints exception
}
}
class task implements Runnable {
@Override
public void run() {
System.out.println("Entering 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 in a custom ThreadPoolExecutor
By overriding afterExecute, you 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, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(10)) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
if (t != null) {
System.out.println("afterExecute caught exception from execute: " + t.getMessage());
}
if (r instanceof FutureTask) {
try {
((Future<?>) r).get();
} catch (Exception e) {
System.out.println("afterExecute caught exception from submit: " + e);
}
}
}
};
executorService.execute(new task());
executorService.submit(new task());
}
}
class task implements Runnable {
@Override
public void run() {
System.out.println("Entering task method!!!");
int i = 1 / 0;
}
}This approach prints exception information for both submission methods, providing a unified handling strategy.
In summary, execute propagates exceptions directly, while submit hides them inside a Future. Use Future.get(), a global uncaught exception handler, or override afterExecute to ensure exceptions are not lost.
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.
