Backend Development 8 min read

How Java Thread Pools Handle Exceptions: execute vs submit

This article investigates how Java's java.util.concurrent.ExecutorService thread pools react to exceptions when tasks are submitted via execute versus submit, providing source‑code experiments, observed outcomes, and a detailed analysis of the underlying JDK implementation.

JD Tech
JD Tech
JD Tech
How Java Thread Pools Handle Exceptions: execute vs submit

The article examines the behavior of Java java.util.concurrent.ExecutorService thread pools when a task throws an exception, comparing the execute and submit submission methods.

1. execute() test

Test code:

public class ThreadPoolExecutorDeadTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = buildThreadPoolExecutor();
        executorService.execute(() -> exeTask("execute"));
        executorService.execute(() -> exeTask("execute"));
        executorService.execute(() -> exeTask("execute-exception"));
        executorService.execute(() -> exeTask("execute"));
        executorService.execute(() -> exeTask("execute"));
        Thread.sleep(5000);
        System.out.println("再次执行任务=======================");
        executorService.execute(() -> exeTask("execute"));
        executorService.execute(() -> exeTask("execute"));
        executorService.execute(() -> exeTask("execute"));
        executorService.execute(() -> exeTask("execute"));
        executorService.execute(() -> exeTask("execute"));
    }
    public static ExecutorService buildThreadPoolExecutor() {
        return new ThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1000), new ThreadFactoryBuilder().setNameFormat("test-%s").build(),
                new ThreadPoolExecutor.CallerRunsPolicy());
    }
    private static void exeTask(String name) {
        String printStr = "[thread-name:" + Thread.currentThread().getName() + ",执行方式:" + name + "]";
        if ("execute-exception".equals(name)) {
            throw new RuntimeException(printStr + ", 我抛异常了");
        } else {
            System.out.println(printStr);
        }
    }
}

Result: the thread that throws an exception is removed from the pool and a new thread is created for subsequent tasks.

Conclusion for execute() : an uncaught exception causes the offending thread to be terminated and replaced by a fresh thread.

2. submit() test

Test code:

public class ThreadPoolExecutorDeadTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = buildThreadPoolExecutor();
        executorService.submit(() -> exeTask("execute"));
        executorService.submit(() -> exeTask("execute"));
        executorService.submit(() -> exeTask("execute-exception"));
        executorService.submit(() -> exeTask("execute"));
        executorService.submit(() -> exeTask("execute"));
        Thread.sleep(5000);
        System.out.println("再次执行任务=======================");
        executorService.submit(() -> exeTask("execute"));
        executorService.submit(() -> exeTask("execute"));
        executorService.submit(() -> exeTask("execute"));
        executorService.submit(() -> exeTask("execute"));
        executorService.submit(() -> exeTask("execute"));
    }
    public static ExecutorService buildThreadPoolExecutor() {
        return new ThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(1000), new ThreadFactoryBuilder().setNameFormat("test-%s").build(),
                new ThreadPoolExecutor.CallerRunsPolicy());
    }
    private static void exeTask(String name) {
        String printStr = "[thread-name:" + Thread.currentThread().getName() + ",执行方式:" + name + "]";
        if ("execute-exception".equals(name)) {
            throw new RuntimeException(printStr + ", 我抛异常了");
        } else {
            System.out.println(printStr);
        }
    }
}

Result: the exception does not propagate to the caller, no stack trace is printed, and the thread remains in the pool; no new thread is created.

Conclusion for submit() : the exception is captured inside the returned Future ; the original worker thread stays alive and is reused.

3. Source‑code analysis

Investigation of java.util.concurrent.AbstractExecutorService#submit shows it wraps the task in a FutureTask , which handles exceptions internally. The execute path directly enqueues the runnable, so an uncaught exception terminates the worker thread, triggering ThreadPoolExecutor#processWorkerExit to replace it.

Key observations:

execute(): exception → thread removal → new thread creation.

submit(): exception → stored in Future → no thread removal.

Both methods do not affect other threads in the pool.

Summary : When a task submitted via execute throws an uncaught exception, the thread pool discards the faulty thread and spawns a replacement; when the same task is submitted via submit , the exception is captured by the Future , the thread stays alive, and no new thread is created.

JavaconcurrencythreadpoolExecutorServiceExceptionHandling
JD Tech
Written by

JD Tech

Official JD technology sharing platform. All the cutting‑edge JD tech, innovative insights, and open‑source solutions you’re looking for, all in one place.

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.