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.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
How to Capture Exceptions from Java ThreadPoolExecutor: submit vs execute

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:

Result of execute showing exception
Result of execute showing exception

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.

Result of try‑catch
Result of try‑catch

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;
    }
}
Result of UncaughtExceptionHandler
Result of UncaughtExceptionHandler

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;
    }
}
Result of afterExecute handling both execute and submit
Result of afterExecute handling both execute and submit

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaExecutorServiceThreadPoolExecutorFutureexceptionhandling
Code Ape Tech Column
Written by

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

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.