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 orTerminateProcesson 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
removeShutdownHookduring shutdown itself (i.e., from inside the shutdown sequence) throwsIllegalStateException.
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
| Rule | Details |
|---|---|
Hook is a Thread | Pass any Thread (or lambda wrapped in one) |
| Must be registered before shutdown starts | Registering during shutdown throws IllegalStateException |
| No ordering guarantee between multiple hooks | Run concurrently; use one hook for ordered steps |
| Time budget | Keep hooks fast — if they block, the JVM waits (potentially forever) |
System.exit() inside a hook | Don’t call it — causes infinite recursion |
| Daemon threads | Shutdown hooks are non-daemon by default; daemon threads stop without a hook |
Not invoked on SIGKILL / TerminateProcess | Hard 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:
- Change JVM state to “shutdown” — new hook registrations from this point throw
IllegalStateException. - Start all registered hook threads — each runs in its own thread, concurrently.
- Wait for all hooks to finish —
join()is effectively called on each. - Run uninvoked finalizers (if
runFinalizersOnExitwas enabled, which is deprecated and strongly discouraged). - 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 sendingSIGTERMto 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.
Related Topics
- 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