Backend Development 15 min read

Handling Exceptions in Java ThreadPool: submit vs execute and Practical Solutions

This article explains why exceptions submitted to a Java ThreadPool via submit are not printed, 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.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Handling Exceptions in Java ThreadPool: submit vs execute and Practical Solutions

In real‑world development, thread pools are frequently used, but when a task throws an exception after being submitted, the handling differs between submit and execute . The article first demonstrates the behavior with a simple Java program that creates a fixed thread pool, submits a task that divides by zero, and shows that execute prints the exception while submit does not.

public class ThreadPoolException {
    public static void main(String[] args) {
        //创建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        //当线程池抛出异常后 submit无提示,其他线程继续执行
        executorService.submit(new task());
        //当线程池抛出异常后 execute抛出异常,其他线程继续执行新任务
        executorService.execute(new task());
    }
}

//任务类
class task implements Runnable {
    @Override
    public void run() {
        System.out.println("进入了task方法!!!");
        int i = 1/0;
    }
}

The result shows that submit silently discards the exception, which is unsuitable for production because the failure is invisible. To obtain the exception, one must call Future.get() on the returned Future object.

Future
submit = executorService.submit(new task());
submit.get();

Three practical solutions are then presented:

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("进入了task方法!!!");
            int i = 1/0;
        } catch (Exception e) {
            System.out.println("使用了try‑catch捕获异常" + e);
        }
    }
}

Both submit and execute now capture the exception and print it.

Solution 2: Use Thread.setDefaultUncaughtExceptionHandler

Define a custom thread factory that sets an UncaughtExceptionHandler for each thread, allowing uncaught exceptions from tasks submitted via execute to be logged.

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("线程工厂设置的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("==================为检验打印结果,1秒后执行execute方法");
        executorService.execute(new task());
    }
}

class task implements Runnable {
    @Override
    public void run() {
        System.out.println("进入了task方法!!!");
        int i = 1/0;
    }
}

Here, execute triggers the handler, while submit still does not because the exception is captured inside the FutureTask .

Solution 3: Override afterExecute in a custom ThreadPoolExecutor

By extending ThreadPoolExecutor and overriding afterExecute(Runnable r, Throwable t) , one can process exceptions from both execute and submit . For submit , the runnable is a FutureTask , so the method checks its type and calls future.get() to retrieve the exception.

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获取到execute异常: " + t.getMessage());
                }
                if (r instanceof FutureTask) {
                    try {
                        ((Future
) r).get();
                    } catch (Exception e) {
                        System.out.println("afterExecute获取到submit异常: " + e);
                    }
                }
            }
        };
        executorService.execute(new task());
        executorService.submit(new task());
    }
}

class task implements Runnable {
    @Override
    public void run() {
        System.out.println("进入了task方法!!!");
        int i = 1/0;
    }
}

This approach ensures that exceptions from both submission methods are logged and can be handled uniformly.

In summary, when using a thread pool, execute propagates uncaught exceptions to the thread’s UncaughtExceptionHandler , while submit captures them inside the returned Future . To avoid losing exception information, developers should either use Future.get() , add explicit try‑catch blocks, set a default uncaught exception handler via a custom thread factory, or override afterExecute in a custom executor.

JavaConcurrencythreadpoolExecutorServiceExceptionHandling
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

login 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.