Graceful Shutdown of Spring Boot Applications Using Linux Signals and Actuator
The article explains how to achieve graceful shutdown of Spring Boot services by handling Linux signals, registering JVM shutdown hooks, using Spring's ContextClosedEvent and Actuator endpoint, and provides practical code examples for safely terminating thread pools, sockets, and other resources.
What Is Graceful Shutdown
Graceful shutdown means that before an application process is terminated it notifies the running code to release resources such as thread pools, sockets, temporary files, and both heap and off‑heap memory, and to deregister from service registries.
Thread pool: shutdown() (no new tasks) or shutdownNow() (interrupt running tasks via Thread.interrupt ).
Socket connections (e.g., Netty, MQ).
Notify service registries (e.g., Eureka) to go offline quickly.
Clean temporary files (e.g., POI generated files).
Release various heap and off‑heap memory.
Forcefully killing a process can cause data loss, unrecoverable state, or data inconsistency in distributed environments.
kill Command
kill -9 pid simulates a hard crash (SIGKILL), while kill -15 pid (SIGTERM) asks the application to shut down gracefully, allowing it to finish pending work.
# View JVM process IDs
jps
# List all signal names
kill -l
# Linux signal constants
# INT SIGINT 2 Ctrl+C interrupt
# TERM SIGTERM 5 Software termination signal
# KILL SIGKILL 9 Forceful termination
# Send signals
kill -9 pid # force kill
kill -15 pid # graceful terminationThink: How does the JVM handle Linux signals?
The JVM registers a custom SignalHandler at startup; when a signal arrives the handler invokes Shutdown.exit to start the shutdown sequence.
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
Calling Runtime.getRuntime().addShutdownHook(Thread hook) registers a hook that the JVM executes when it begins shutting down.
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);
}
}Spring 3.2.12 Context Closed Event
Spring publishes a ContextClosedEvent when the application context is closed. The LifecycleProcessor.onClose method then stops all beans that implement Lifecycle .
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
publishEvent(new ContextClosedEvent(this));
// stop Lifecycle beans
getLifecycleProcessor().onClose();
// destroy beans, close BeanFactory, etc.
}
}Spring Boot Actuator Shutdown Endpoint
Spring Boot’s spring-boot-starter-actuator module provides a REST endpoint ( /shutdown ) that triggers a graceful shutdown by invoking AbstractApplicationContext.close() .
# Enable the shutdown endpoint
endpoints.shutdown.enabled=true
# Disable password protection (for internal use only)
endpoints.shutdown.sensitive=false
management.context-path=/manage
management.port=8088
management.address=127.0.0.1When the endpoint is called (e.g., curl -X POST http://127.0.0.1:8088/shutdown ) the actuator creates a new thread that eventually calls context.close() , which runs all registered shutdown hooks and releases resources.
@ConfigurationProperties(prefix = "endpoints.shutdown")
public class ShutdownEndpoint extends AbstractEndpoint
> {
private ConfigurableApplicationContext context;
public Map
invoke() {
if (this.context == null) return NO_CONTEXT_MESSAGE;
Thread thread = new Thread(() -> this.context.close());
thread.setContextClassLoader(this.getClass().getClassLoader());
thread.start();
return SHUTDOWN_MESSAGE;
}
}In production environments the shutdown URL should be secured with Spring Security or network restrictions.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
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.