Back to Daemon Threads & JVM Shutdown
Q3Question 3 of 3

What is a JVM shutdown hook?

Key Concepts
  • A shutdown hook is a registered Thread that the JVM runs when it begins an orderly shutdown.
  • Runs when: last non-daemon thread finishes, System.exit is called, or SIGTERM/SIGINT is received.
  • Does NOT run: kill -9 (SIGKILL), power loss, or a hard JVM native crash.
  • Hooks must be fast: Kubernetes gives 30 seconds between SIGTERM and SIGKILL.
  • Pattern: flip running flag, executor.shutdown(), awaitTermination with timeout, shutdownNow(), close resources.
  • Do not call System.exit inside a hook (deadlocks shutdown).
  • Never rely on hooks for correctness-critical persistence; commit transactionally as you go.

Definition

A shutdown hook runs on an orderly stop (SIGTERM, System.exit, Ctrl-C) but never on kill -9, so use it for fast graceful cleanup, not guaranteed work.
  • Shutdown hook – a registered Thread the JVM runs during orderly shutdown.
  • Runs when – last non-daemon thread finishes, System.exit(), SIGTERM, Ctrl-C (SIGINT).
  • Does NOT run – kill -9 (SIGKILL), power loss, native JVM crash.
  • Must be fast – orchestrators give a limited grace period before SIGKILL.
  • Pattern – stop accepting work, drain in-flight tasks, flush buffers, close connections.
  • Rule – never rely on hooks for correctness; commit critical data transactionally.

When National Bank of India closes for the day, the manager rings a bell and staff finish the current customer, lock the vault, and switch off the lights. That is the shutdown hook. If the building suddenly loses power (kill -9), nobody gets to finish anything.

Understanding the Concepts

How do you register a shutdown hook?

A shutdown hook is a Thread that you register with the JVM. The JVM starts it during orderly shutdown. The thread must be created but not started by you; the JVM starts it.

JAVA
package com.bank.shutdown;

public class ShutdownHookRegistration {
    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("Shutdown hook: flushing audit buffer...");
            auditBuffer.flush();
            connectionPool.close();
            System.out.println("Shutdown hook: cleanup complete.");
        }, "cleanup-hook"));
    }
}

When does it run and when does it not?

| Trigger | Hook Runs? | Notes |
|---------|------------|-------|
| Last non-daemon thread finishes | Yes | JVM exits naturally |
| System.exit(code) | Yes | Explicit shutdown |
| SIGTERM (Kubernetes pod stop) | Yes | k8s sends SIGTERM first |
| Ctrl-C (SIGINT) | Yes | Developer stop |
| kill -9 (SIGKILL) | No | Immediate process death |
| Power loss | No | Hardware failure |
| Native JVM crash (segfault) | No | Unrecoverable error |
| OutOfMemoryError | Maybe | Depends on how JVM handles it |

What is the graceful shutdown pattern?

The standard pattern stops accepting new work, drains in-flight tasks with a bounded timeout, and then closes resources.

JAVA
package com.bank.shutdown;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

public class GracefulShutdown {

    private volatile boolean running = true;
    private final ExecutorService executor;

    public GracefulShutdown(ExecutorService executor) {
        this.executor = executor;
        Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown, "graceful-shutdown"));
    }

    private void shutdown() {
        running = false;              // signal to stop accepting work
        executor.shutdown();          // stop accepting new tasks
        try {
            if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
                var dropped = executor.shutdownNow();  // interrupt stragglers
                System.err.println("Dropped " + dropped.size() + " tasks");
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
        System.out.println("Shutdown complete.");
    }

    public boolean isRunning() { return running; }
}

How does Kubernetes interact with shutdown hooks?

Kubernetes sends SIGTERM to the pod's main process. The JVM receives SIGTERM and runs registered shutdown hooks. Kubernetes waits for terminationGracePeriodSeconds (default 30 seconds) before sending SIGKILL. Your shutdown hook must complete within this grace period.

DIAGRAM
Kubernetes:  SIGTERM ──── 30s grace ──── SIGKILL
JVM:         hooks start ──── drain tasks ──── exit

How do multiple shutdown hooks interact?

All registered hooks start concurrently in separate threads. They run in no guaranteed order. If you need ordering, use a single hook that performs steps sequentially.

JAVA
// WRONG: relying on hook ordering (undefined)
Runtime.getRuntime().addShutdownHook(new Thread(() -> closeConnections()));
Runtime.getRuntime().addShutdownHook(new Thread(() -> flushBuffers()));
// Connections might close BEFORE buffers flush!

// CORRECT: single hook with ordered steps
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    flushBuffers();      // step 1
    closeConnections();  // step 2
}));

What are common mistakes?

JAVA
// WRONG: calling System.exit inside a hook (deadlocks)
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    cleanup();
    System.exit(0);  // DEADLOCK: shutdown is already in progress
}));

// WRONG: relying on hook for critical data
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    saveUncommittedPayments();  // may not run on kill -9!
}));

// CORRECT: commit transactionally as you go
@Transactional
void processPayment(Payment payment) {
    paymentRepo.save(payment.withStatus("COMPLETED"));
    // Committed immediately, survives any shutdown scenario
}

How does Spring Boot handle graceful shutdown?

Spring Boot 2.3+ supports graceful shutdown with server.shutdown=graceful and spring.lifecycle.timeout-per-shutdown-phase=30s. When SIGTERM is received, Spring stops accepting new requests, waits for in-flight requests to complete, and then destroys beans in reverse order.

What are common interview follow-up questions?

  • How do multiple shutdown hooks interact? They start concurrently, no order guaranteed.
  • What is the relationship between daemon threads and shutdown hooks? When all non-daemon threads finish, shutdown hooks run, and then daemon threads are killed.
  • Can you remove a registered shutdown hook? Yes, with Runtime.removeShutdownHook(thread), but only before shutdown has started.

Visual Overview

DIAGRAM
  JVM Shutdown Sequence:

  Trigger: SIGTERM / System.exit / last non-daemon thread
       │
       ▼
  JVM begins orderly shutdown
       │
       ▼
  All registered hooks start concurrently
  ┌──────────────────────────────────┐
  │ Hook 1: executor.shutdown()      │
  │         awaitTermination(10s)    │
  │         shutdownNow() if timeout │
  │ Hook 2: auditBuffer.flush()      │
  │ Hook 3: connectionPool.close()   │
  └──────────────────────────────────┘
       │
       ▼
  All hooks complete → JVM exits

  kill -9: JVM destroyed immediately, no hooks run

Key Takeaway

A JVM shutdown hook is a thread registered with Runtime.getRuntime().addShutdownHook that runs during orderly shutdown: when System.exit is called, SIGTERM is received, or the last non-daemon thread finishes. It does NOT run on kill -9, power loss, or native crashes.

The pattern: stop accepting work, drain in-flight tasks with a bounded timeout, then close resources. The hook must be fast because orchestrators send SIGKILL after a grace period.

At National Bank of India our payment processor uses a shutdown hook to drain in-flight payments on deploy, and persists each payment transactionally because an emergency kill -9 would skip the hook entirely.

Remember one sentence: a shutdown hook runs on orderly stops like SIGTERM but never on kill -9, so use it for fast graceful cleanup and rely on transactional commits for anything that must survive.

---

Share
Use ← → arrow keys to navigate