Skip to content
Java multithreading 5 min read

Shutdown Hook

When your Java application exits — whether it finishes normally, the user presses Ctrl+C, or the OS sends a termination signal — you often need to run some last-minute cleanup: close database connections, flush logs, release file locks, or save state. Java’s shutdown hook mechanism gives you a reliable way to do exactly that.

What Is a Shutdown Hook?

A shutdown hook is a Thread you register with the JVM. When the JVM begins its shutdown sequence, it starts all registered shutdown-hook threads and waits for them to finish before halting. You register one using Runtime.getRuntime().addShutdownHook(Thread).

The JVM shutdown sequence is triggered by:

  • The last non-daemon thread exits normally
  • System.exit(int) is called
  • The user sends a termination signal (Ctrl+C, SIGTERM, SIGINT)
  • The OS initiates shutdown

Note: Shutdown hooks do not run if the JVM is killed with SIGKILL (kill -9) on Unix or TerminateProcess on Windows. Those are hard kills — no cleanup is possible.

Registering a Shutdown Hook

public class ShutdownHookDemo {
    public static void main(String[] args) throws InterruptedException {
        // Register the hook BEFORE you need it
        Thread hook = new Thread(() -> {
            System.out.println("Shutdown hook running — cleaning up...");
            // e.g. close database connections, flush buffers
        });
        Runtime.getRuntime().addShutdownHook(hook);

        System.out.println("Application started. Press Ctrl+C to stop.");
        Thread.sleep(10_000); // simulate work
        System.out.println("Application finished normally.");
    }
}

Output (Ctrl+C after a couple of seconds):

Application started. Press Ctrl+C to stop.
^C
Shutdown hook running — cleaning up...

Output (normal completion):

Application started. Press Ctrl+C to stop.
Application finished normally.
Shutdown hook running — cleaning up...

The hook runs in both cases.

A Realistic Example: Closing a Resource

Here is a more practical pattern — wrapping a resource in a class and registering its cleanup as a shutdown hook.

public class DatabaseConnection {
    private final String url;

    public DatabaseConnection(String url) {
        this.url = url;
        // Register cleanup when JVM exits
        Runtime.getRuntime().addShutdownHook(new Thread(this::close));
        System.out.println("Connected to: " + url);
    }

    public void doWork() {
        System.out.println("Running queries on " + url);
    }

    private void close() {
        System.out.println("Closing connection to: " + url);
        // connection.close() would go here
    }

    public static void main(String[] args) {
        DatabaseConnection db = new DatabaseConnection("jdbc:mysql://localhost/mydb");
        db.doWork();
        // JVM exits — hook closes the connection automatically
    }
}

Output:

Connected to: jdbc:mysql://localhost/mydb
Running queries on jdbc:mysql://localhost/mydb
Closing connection to: jdbc:mysql://localhost/mydb

Removing a Shutdown Hook

You can also deregister a hook before the JVM shuts down if it is no longer needed:

Thread hook = new Thread(() -> System.out.println("Cleanup"));
Runtime.getRuntime().addShutdownHook(hook);

// Later, if cleanup is no longer needed:
boolean removed = Runtime.getRuntime().removeShutdownHook(hook);
System.out.println("Hook removed: " + removed); // true if it was registered

Warning: Calling removeShutdownHook during shutdown itself (i.e., from inside the shutdown sequence) throws IllegalStateException.

Multiple Shutdown Hooks

You can register more than one hook. They all run concurrently — the JVM starts them all at the same time and waits for each to finish. There is no guaranteed execution order between hooks.

Runtime rt = Runtime.getRuntime();

rt.addShutdownHook(new Thread(() -> System.out.println("Hook A: flushing logs")));
rt.addShutdownHook(new Thread(() -> System.out.println("Hook B: closing DB")));
rt.addShutdownHook(new Thread(() -> System.out.println("Hook C: releasing locks")));

Output (order may vary):

Hook B: closing DB
Hook A: flushing logs
Hook C: releasing locks

If you need ordered cleanup (e.g., flush logs then close DB), use a single hook that coordinates the steps in the right order.

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    System.out.println("Step 1: flushing logs");
    System.out.println("Step 2: closing DB");
    System.out.println("Step 3: releasing locks");
}));

Rules and Limitations

RuleDetails
Hook is a ThreadPass any Thread (or lambda wrapped in one)
Must be registered before shutdown startsRegistering during shutdown throws IllegalStateException
No ordering guarantee between multiple hooksRun concurrently; use one hook for ordered steps
Time budgetKeep hooks fast — if they block, the JVM waits (potentially forever)
System.exit() inside a hookDon’t call it — causes infinite recursion
Daemon threadsShutdown hooks are non-daemon by default; daemon threads stop without a hook
Not invoked on SIGKILL / TerminateProcessHard kills bypass all hooks

Tip: Always add a timeout mechanism inside a hook if it does anything that could block (like a network call). A stalled shutdown hook freezes the JVM exit.

Under the Hood

When System.exit(status) is called, it invokes Runtime.halt(status) after running the shutdown sequence. Here is what the JVM does internally:

  1. Change JVM state to “shutdown” — new hook registrations from this point throw IllegalStateException.
  2. Start all registered hook threads — each runs in its own thread, concurrently.
  3. Wait for all hooks to finishjoin() is effectively called on each.
  4. Run uninvoked finalizers (if runFinalizersOnExit was enabled, which is deprecated and strongly discouraged).
  5. Halt the JVM — calls the native exit() system call.

Shutdown hooks live in the ApplicationShutdownHooks class inside the JDK (java.lang package, package-private). Internally, hooks are stored in an IdentityHashMap<Thread, Thread> keyed by the thread itself, which ensures you cannot register the same thread twice.

Each hook thread inherits the security context and thread group of the thread that called addShutdownHook. This matters in environments with a SecurityManager (rare in modern Java, fully removed in Java 17+).

Note: Finalizers (Object.finalize()) were deprecated in Java 9 and removed in Java 18. Do not rely on them for cleanup — shutdown hooks are the correct mechanism.

Practical Tips

  • Keep hooks short and fast. A hook doing heavy I/O can delay shutdown significantly.
  • Use one hook per logical subsystem — but remember concurrent execution.
  • Log at the start of every hook so you can tell in production logs which hooks ran.
  • Test your hooks by calling System.exit(0) explicitly in development or by sending SIGTERM to your process.
  • Thread safety: If your hook shares state with the main application thread, synchronize carefully — both may run simultaneously right before shutdown.
// Pattern: central cleanup coordinator
public class AppLifecycle {
    private static final Object lock = new Object();
    private static boolean cleanedUp = false;

    static {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            synchronized (lock) {
                if (!cleanedUp) {
                    System.out.println("Running cleanup...");
                    cleanedUp = true;
                }
            }
        }));
    }
}

This guards against double-cleanup if both normal exit and a signal fire close together.

  • Daemon Threads — threads that stop without cleanup when the JVM exits; contrast with shutdown hooks
  • Thread Life Cycle — understand thread states to write better hook logic
  • Multithreading — the broader context for concurrent hook execution
  • Thread Pool — graceful executor shutdown typically happens inside a shutdown hook
  • Callable & Future — cancel pending async tasks as part of a clean shutdown strategy
  • finally Block — the per-method cleanup counterpart to JVM-level shutdown hooks
Last updated June 13, 2026
Was this helpful?