Graceful Shutdown of Spring Boot Applications Using JVM Signals and Actuator
The article explains how to achieve graceful shutdown of Spring Boot services by handling Linux kill signals, registering JVM shutdown hooks, and exposing an Actuator REST endpoint that safely releases resources, stops beans, and terminates the JVM without data loss.
In many business systems that run Spring Boot with an embedded Tomcat, administrators often use the Linux kill command to stop processes, but a proper graceful shutdown requires careful handling.
What is graceful shutdown
Graceful shutdown means ensuring that when an application stops, it notifies processes to release resources.
Thread pools: shutdown() (no new tasks, wait) or shutdownNow() (calls Thread.interrupt ).
Socket connections such as Netty or MQ.
Notify registration centers for quick deregistration, e.g., Eureka.
Clean temporary files, e.g., POI.
Release various heap and off‑heap memory.
Forcefully terminating a process can cause data loss, unrecoverable state, or data inconsistency in distributed environments.
kill command
kill -9 pid simulates a crash, while kill -15 pid sends SIGTERM, allowing the application to shut down gracefully; sometimes the application may still fail to stop.
# View JVM process pid
jps
# List all signal names
kill -l
# Windows signal constants
# INT SIGINT 2 Ctrl+C interrupt
# ILL SIGILL 4 Illegal instruction
# FPE SIGFPE 8 floating point exception
# SEGV SIGSEGV 11 segment violation
# TERM SIGTERM 5 Software termination signal from kill
# BREAK SIGBREAK 21 Ctrl‑Break sequence
# ABRT SIGABRT 22 abnormal termination triggered by abort call
# Linux signals
# HUP SIGHUP 1 terminal hangup
# INT SIGINT 2 interrupt (Ctrl+C)
# QUIT SIGQUIT 3 quit (Ctrl+\)
# KILL SIGKILL 9 forced termination
# TERM SIGTERM 15 termination
# CONT SIGCONT 18 continue (opposite of STOP)
# STOP SIGSTOP 19 pause (Ctrl+Z)
# ...JVM handles Linux signals via a custom SignalHandler loaded at startup; when the JVM shuts down, the corresponding handler is invoked.
public interface SignalHandler {
SignalHandler SIG_DFL = new NativeSignalHandler(0L);
SignalHandler SIG_IGN = new NativeSignalHandler(1L);
void handle(Signal var1);
}
class Terminator {
private static SignalHandler handler = null;
static void setup() {
if (handler == null) {
SignalHandler var0 = new SignalHandler() {
public void handle(Signal var1) {
Shutdown.exit(var1.getNumber() + 128);
}
};
handler = var0;
try {
Signal.handle(new Signal("INT"), var0);
} catch (IllegalArgumentException e) {}
try {
Signal.handle(new Signal("TERM"), var0);
} catch (IllegalArgumentException e) {}
}
}
}Runtime.addShutdownHook registers a hook that runs when the JVM exits:
public class Runtime {
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
}
class ApplicationShutdownHooks {
private static IdentityHashMap
hooks;
static synchronized void add(Thread hook) {
if (hooks == null) throw new IllegalStateException("Shutdown in progress");
if (hook.isAlive()) throw new IllegalArgumentException("Hook already running");
if (hooks.containsKey(hook)) throw new IllegalArgumentException("Hook previously registered");
hooks.put(hook, hook);
}
// ... execution of hooks, finalizers, halt, etc.
}Spring 3.2.12 uses ContextClosedEvent and LifecycleProcessor.onClose() to stop beans during shutdown.
public abstract class AbstractApplicationContext extends DefaultResourceLoader {
public void registerShutdownHook() {
if (this.shutdownHook == null) {
this.shutdownHook = new Thread() {
@Override public void run() { doClose(); }
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
protected void doClose() {
// publish ContextClosedEvent, stop Lifecycle beans, destroy beans, close BeanFactory, etc.
}
}Spring Boot provides a REST endpoint via spring-boot-starter-actuator to trigger graceful shutdown:
# Enable shutdown endpoint
endpoints.shutdown.enabled=true
# Disable password protection
endpoints.shutdown.sensitive=false
management.context-path=/manage
management.port=8088
management.address=127.0.0.1
# Enable security if needed
endpoints.shutdown.sensitive=true
security.user.name=admin
security.user.password=secret
management.security.role=SUPERUSERCalling POST /shutdown invokes ShutdownEndpoint , which ultimately calls AbstractApplicationContext.close() to run all shutdown hooks, stop beans, and halt the JVM.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.