JVM Shutdown Sequence and Spring Boot's Shutdown Hook
Nov 15, 2025
Overview
In this article, we’ll look at the JVM shutdown behavior. Specifically, we’ll learn about the specific process the JVM undertakes during a graceful shutdown. Additionally, we’ll learn how Spring Boot hooks into this behavior to ensure beans are properly cleaned up during graceful shutdown.
Graceful vs. Non-graceful Shutdown
Generally speaking, applications can be shut down in a graceful and non-graceful (a.k.a. forceful shutdown) way.
Visually:

Graceful Shutdown
During a graceful shutdown, the application process itself is made aware that there’s an intention to shut down. Then, it initiates a series of processes to stop accepting new tasks and finalize the running tasks.
To trigger a graceful shutdown, we typically send POSIX signals, such as SIGINT and SIGTERM, to the application process.
Note: difference between
SIGINTandSIGTERM?SIGINTis meant for interactive program where upon receiving such signal, should return to the menu. But for non-interactive programs, most treat it the same asSIGTERM. – Reddit
Crucially, graceful shutdown prevents any partially processed tasks that might lead to data corruption. Concretely, it gives the application process an opportunity to run cleanup tasks before yielding to the kernel for the final cleanup.
In most cases, a graceful shutdown comes with a timeout. Beyond the graceful duration, a non-graceful shutdown will be initiated.
Non-graceful Shutdown
Non-graceful shutdown of an application means that the application process get terminated immediately without a chance to run its usual shutdown sequence.
For instance, we can instantly kill off an application process in Linux using the kill -9 $PID command. The command sends the SIGKILL signal to the process.
Upon receiving the SIGKILL signal, the kernel stops scheduling the target process. Subsequently, it begins reclaiming all the resources of the application process.
Importantly, the application proess is entirely unaware of the termination throughout the entire forceful shutdown. As a result, the application process cannot anticipate the signal and run any cleanup processes.
JVM Shutdown Sequence
In the JVM Runtime specification, the documentation clearly defines the trigger for the shutdown, as well as the shutdown sequence itself.
Visually:

Triggers
When one of the following events happens, the JVM initiates the shutdown sequence:
- When there aren’t any live non-daemon threads.
- When the
System.exitis invoked - When the JVM processes receive signals from the OS. In a POSIX system, signals like
SIGINTandSIGTERMtrigger the shutdown process.
Shutdown Sequence
- Starts all shutdown hooks concurrently
- Wait for all the shutdown hooks to return.
- Calls
Runtime.halt() - Kernel-level process termination (i.e., clearing scheduling queue, reclaiming resources).
Thread Interruption During Shutdown?
A commonly misunderstood aspect of the JVM shutdown sequence is that the JVM Runtime will interrupt (using the interrupt() method) all the living, non-daemon threads as part of the shutdown process.
However, as we can see from the documentation, the shutdown process does not interrupt the non-daemon threads during its shutdown sequence.
If we have running threads that needs to be interrupted during the shutdown so that it can be cleaned up, it’s important to ensure that a shutdown hook is installed that invoke the interrupt method on the thread.
Interrupting Threads Using Shutdown Hook
To visualize the difference, we’ll create two examples.
The first example shows that without installing a shutdown hook, no InterruptedException will be thrown in our processing during shutdown:
public class NoShutdownHookDemo {
public static void main(String[] args) throws Exception {
Thread worker = new Thread(() -> {
try {
System.out.println("[Worker] Started. Going to sleep...");
Thread.sleep(10_000);
System.out.println("[Worker] Woke up normally.");
} catch (InterruptedException e) {
System.out.println("[Worker] Interrupted!");
}
});
worker.start();
System.out.println("[Main] Press Ctrl+C now...");
worker.join();
}
}
Running the code above, we’ll get the following output:
[Main] Press Ctrl+C now...
[Worker] Started. Going to sleep...
Next, we’ll register a shutdown hook that interrupts our worker thread:
public class WithShutdownHookDemo {
public static void main(String[] args) throws Exception {
Thread worker = new Thread(() -> {
try {
System.out.println("[Worker] Started. Going to sleep...");
Thread.sleep(10_000);
System.out.println("[Worker] Woke up normally.");
} catch (InterruptedException e) {
System.out.println("[Worker] Interrupted! Cleaning up...");
}
});
// Register shutdown hook that interrupts worker thread
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("[ShutdownHook] JVM shutting down. Interrupting worker...");
worker.interrupt();
}));
worker.start();
System.out.println("[Main] Press Ctrl+C now...");
worker.join();
}
}
Subsequently, when we run the program and send a SIGINT signal using CTRL+C, we’ll observe the following output:
[Main] Press Ctrl+C now...
[Worker] Started. Going to sleep...
^C[ShutdownHook] JVM shutting down. Interrupting worker...
[Worker] Interrupted! Cleaning up...
Spring Boot Shutdown and JVM Shutdown Hook
To the application context in Spring Boot is aware of the shutdown, Spring Boot registers a JVM shutdown hook for each of the SpringApplication. The shutdown hook registered will invoke the appropriate destruction lifecycle methods when the shutdown hook is invoked during the JVM shutdown sequence.
For example, the Spring Boot shutdown logic will call the destroy method on beans that implement the DisposableBean interface.
Besides that, the shutdown hook also invokes the methods that are annotated with the @PreDestroy annotation.
Conclusion
In this article, we’ve learned that JVM invokes and wait on all the shutdown hooks as part of its shutdown process. Importantly, we’ve seen that non-daemon threads by default are not interrupted by the Runtime during the usual shutdown sequence.
Additionally, we’ve learned that in Spring application registers a shutdown hook with the JVM so that they gets notified about the JVM shutdown event. This hook, in turn, initiates the context shutdown process, which includes invoking methods annotated with the @PreDestory annotation, and calling the destroy method on classes that implement the DisposableBean interface.