skip to content

What is the difference between shutdown() and shutdownNow() on an ExecutorService, and what does each return or do to in-flight tasks?

level: middleimportance: must knowfreq 78%

answer

  1. shutdown = graceful drain; shutdownNow = interrupt + return pending
  2. shutdownNow returns List<Runnable> of never-started tasks
  3. interrupt is cooperative — CPU loops ignore it
  4. neither blocks; awaitTermination does the waiting
  5. two-phase: shutdown → await → shutdownNow → await

basics

~10 s

shutdown() stops accepting new tasks but lets already-submitted ones finish. shutdownNow() tries to stop running tasks by interrupting them and returns the list of tasks that never started.

solid answer

~40 s

Both initiate shutdown so no new tasks are accepted, but they differ in how they treat work already in the pool. shutdown() is graceful: it returns void, lets all queued and running tasks complete, then terminates. shutdownNow() is forceful: it drains the queue and returns the List<Runnable> of tasks that were waiting but never started, and it interrupts the threads running active tasks. Crucially, shutdownNow() only *requests* cancellation via Thread.interrupt() — tasks that never check the interrupt flag or call a blocking method that responds to it will keep running. Neither call blocks waiting for completion; you call awaitTermination(timeout) afterward to wait. A robust pattern is shutdown(), await a grace period, then shutdownNow() if it times out, then await again.

code

java · 15 lines
java
void shutdownGracefully(ExecutorService pool) {
    pool.shutdown(); // stop accepting; let current work finish
    try {
        if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
            List<Runnable> dropped = pool.shutdownNow(); // interrupt + reclaim pending
            log.warn("Forced shutdown; {} tasks never started", dropped.size());
            if (!pool.awaitTermination(10, TimeUnit.SECONDS)) {
                log.error("Pool did not terminate");
            }
        }
    } catch (InterruptedException e) {
        pool.shutdownNow();
        Thread.currentThread().interrupt(); // restore the flag
    }
}

go deeper

for a junior

Knows shutdown() stops accepting new work and lets current work finish, while shutdownNow() tries to stop things immediately. Can call shutdown() to let the JVM exit.

for a middle

Articulates the exact contract: queued vs running task handling, the List<Runnable> return, that neither blocks, and that shutdownNow only interrupts.

for a senior

Explains the cooperative-interruption caveat and the canonical two-phase shutdown-then-shutdownNow pattern with awaitTermination; knows RejectedExecutionException on post-shutdown submit.

for a principal

Reasons about shutdown semantics across an application's lifecycle — ordering pool shutdowns by dependency, draining vs dropping in-flight work, persisting returned pending tasks, and ensuring tasks are written to honor interruption.

## What an ExecutorService is An **ExecutorService** is Java's abstraction for a pool of worker threads that run submitted tasks (`Runnable`/`Callable`). You hand it work via `submit`/`execute`; internally it keeps a set of worker threads and a **task queue** (a `BlockingQueue`) of work waiting for a free thread. Because it owns live threads, you must explicitly shut it down — otherwise those (often non-daemon) threads keep the JVM alive forever. ## The two shutdown methods **`shutdown()`** — an *orderly*, graceful shutdown. After you call it: - The pool **stops accepting new tasks**: any further `submit`/`execute` throws `RejectedExecutionException`. - All tasks **already in the queue** and all **currently running** tasks are allowed to finish normally. - It returns **`void`** and **does not block** — it just flips the pool into the SHUTDOWN state and returns immediately. **`shutdownNow()`** — a *forceful* shutdown attempt. After you call it: - It also stops accepting new tasks. - It **drains the work queue** and **returns** the `List<Runnable>` of tasks that were waiting but had **not yet started**. You can log, persist, or resubmit them elsewhere. - For tasks that **are already running**, it calls **`Thread.interrupt()`** on each worker thread to *request* cancellation. - It also does **not block**. ## The critical caveat: interrupt is cooperative `shutdownNow()` cannot force a thread to stop. Java has **no safe way to forcibly kill a thread** (`Thread.stop()` is deprecated and dangerous). Interruption is **cooperative**: setting a thread's interrupt flag only has an effect if the task *cooperates* by either checking `Thread.currentThread().isInterrupted()` periodically, or calling a blocking method (`sleep`, `wait`, `join`, `BlockingQueue.take`, many NIO ops) that throws `InterruptedException` in response. A pure CPU loop like `while (true) { compute(); }` that never checks the flag will **ignore `shutdownNow()` entirely** and run forever. ## How they fit the lifecycle Neither method waits for completion. To actually wait you call **`awaitTermination(timeout, unit)`**, which blocks until the pool is terminated, the timeout elapses, or the calling thread is interrupted. The recommended two-phase pattern (from `ExecutorService`'s own Javadoc) is: call `shutdown()`, `awaitTermination` for a grace period, and if it returns `false` call `shutdownNow()` and `awaitTermination` again. ## Summary table | Aspect | shutdown() | shutdownNow() | |---|---|---| | New tasks | Rejected | Rejected | | Queued (not started) tasks | Run to completion | Returned as List<Runnable>, not run | | Running tasks | Run to completion | Interrupted (cooperative) | | Return value | void | List<Runnable> | | Blocks? | No | No |

  • What happens if you submit a task after calling shutdown()?
    The default policy rejects it: submit/execute throws RejectedExecutionException (handled by the pool's RejectedExecutionHandler, AbortPolicy by default).
  • Why can't shutdownNow() guarantee tasks stop?
    Because Java has no safe forced thread-kill; interruption is cooperative. A task must check the interrupt flag or call an interrupt-responsive blocking method to actually stop.

saying these in an interview costs you the question

  • Claiming shutdownNow() forcibly kills or stops running threads — it only interrupts them
  • Thinking shutdown()/shutdownNow() block until tasks finish (they return immediately)
  • Saying shutdown() discards queued tasks (it runs them; shutdownNow() returns them)
  • Believing a tight CPU loop will stop on shutdownNow()

context