Graceful Shutdown of Spring Boot Applications: kill Commands, Signal Handlers, and Runtime Hooks
This article explains how to achieve graceful shutdown for Java applications by using Linux kill signals, JVM SignalHandler registration, Runtime.addShutdownHook, and Spring Boot's actuator shutdown endpoint, providing code examples and configuration details for safe resource cleanup.
In this technical guide, the author introduces the concept of graceful shutdown for Java services, emphasizing the importance of releasing resources and avoiding data loss when terminating processes.
What Is Graceful Shutdown
Graceful shutdown ensures that an application notifies its threads, sockets, registration centers, temporary files, and memory allocations to clean up before the process exits, preventing data inconsistency in distributed environments.
kill Command
The kill -9 pid command simulates a forced termination, while kill -15 pid sends a termination signal that allows the application to shut down cleanly.
<code># 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
# BREAK SIGBREAK 21 Ctrl-Break sequence
# ABRT SIGABRT 22 Abnormal termination (abort)
# Linux signal constants
# HUP SIGHUP 1 Terminal hangup
# INT SIGINT 2 Interrupt (Ctrl+C)
# QUIT SIGQUIT 3 Quit (Ctrl+\)
# KILL SIGKILL 9 Force kill
# TERM SIGTERM 15 Terminate
# CONT SIGCONT 18 Continue (fg/bg)
# STOP SIGSTOP 19 Stop (Ctrl+Z)</code>JVM Signal Handling
The JVM registers a custom SignalHandler at startup to process Linux signals. When a signal is received, the handler invokes Shutdown.exit with the appropriate exit code.
<code>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) {}
}
}
}</code>Runtime.addShutdownHook
Java provides Runtime.getRuntime().addShutdownHook(Thread hook) to register a hook that runs when the JVM shuts down. The hook can perform cleanup tasks before the process terminates.
<code>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<Thread, Thread> 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);
}
}
class Shutdown {
private static final int RUNNING = 0, HOOKS = 1, FINALIZERS = 2;
private static int state = RUNNING;
private static final Runnable[] hooks = new Runnable[10];
static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
synchronized (lock) {
if (hooks[slot] != null) throw new InternalError("Shutdown hook at slot " + slot + " already registered");
if (!registerShutdownInProgress) {
if (state > RUNNING) throw new IllegalStateException("Shutdown in progress");
} else {
if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook))
throw new IllegalStateException("Shutdown in progress");
}
hooks[slot] = hook;
}
}
// ... (other shutdown sequence methods) ...
}</code>Spring Context Shutdown
Spring registers its own shutdown hook. When the context closes, it publishes a ContextClosedEvent , invokes LifecycleProcessor.onClose() , stops beans, destroys the BeanFactory, and runs custom hooks.
<code>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() {
boolean actuallyClose;
synchronized (this.activeMonitor) {
actuallyClose = this.active && !this.closed;
this.closed = true;
}
if (actuallyClose) {
logger.info("Closing " + this);
publishEvent(new ContextClosedEvent(this));
try { getLifecycleProcessor().onClose(); } catch (Throwable ex) { logger.warn(..., ex); }
destroyBeans();
closeBeanFactory();
onClose();
synchronized (this.activeMonitor) { this.active = false; }
}
}
}
interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}</code>Spring Boot Actuator Shutdown Endpoint
Spring Boot provides a RESTful shutdown endpoint via the spring-boot-starter-actuator module. Enabling it and sending a POST request to /shutdown triggers the same context close logic.
<code># Enable shutdown endpoint
endpoints.shutdown.enabled=true
# Disable authentication (for internal use)
endpoints.shutdown.sensitive=false
management.context-path=/manage
management.port=8088
management.address=127.0.0.1
# With security
endpoints.shutdown.sensitive=true
security.user.name=admin
security.user.password=secret
management.security.role=SUPERUSER</code>The endpoint maps to ShutdownMvcEndpoint , which delegates to ShutdownEndpoint.invoke() . The implementation creates a new thread that calls AbstractApplicationContext.close() after a short delay, allowing the HTTP response to be returned before the JVM terminates.
Conclusion
By combining Linux signals, JVM signal handlers, Runtime shutdown hooks, and Spring Boot's actuator endpoint, developers can achieve a controlled and graceful shutdown of Java services, ensuring resources are released and data integrity is maintained.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
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.