Graceful Shutdown of Java Applications: kill Commands, JVM Hooks, and Spring Boot Actuator
This article explains how to perform a graceful shutdown of Java services by using Linux kill signals, configuring JVM SignalHandler and Runtime shutdown hooks, releasing resources such as thread pools and sockets, and exposing a Spring Boot Actuator endpoint for controlled termination, complete with code examples and configuration details.
Graceful shutdown
Graceful shutdown is the process of releasing resources, stopping acceptance of new work, and completing in‑flight tasks before the JVM exits.
Typical resources to release
Thread pools – shutdown() (no new tasks) or shutdownNow() (interrupt threads)
Network connections (e.g., Netty, message queues)
Service registry – deregister from Eureka or similar
Temporary files (e.g., POI generated files)
Heap and off‑heap memory
Linux kill command
Signal SIGTERM ( kill -15 pid) asks the process to terminate cleanly; SIGKILL ( kill -9 pid) forces immediate termination.
# List JVM processes
jps
# Show signal names
kill -l
# Common signals
# INT SIGINT 2 Ctrl+C interrupt
# TERM SIGTERM 5 Software termination (kill)
# KILL SIGKILL 9 Force termination (kill -9)JVM signal handling
The JVM registers a custom SignalHandler that maps signals to shutdown actions.
public interface SignalHandler {
SignalHandler SIG_DFL = new NativeSignalHandler(0L);
SignalHandler SIG_IGN = new NativeSignalHandler(1L);
void handle(Signal sig);
}
class Terminator {
private static SignalHandler handler = null;
static void setup() {
if (handler == null) {
SignalHandler h = new SignalHandler() {
public void handle(Signal sig) {
Shutdown.exit(sig.getNumber() + 128);
}
};
handler = h;
try {
Signal.handle(new Signal("INT"), h);
Signal.handle(new Signal("TERM"), h);
} catch (IllegalArgumentException ignored) {}
}
}
}Runtime shutdown hook
Spring registers a JVM shutdown hook that invokes Runtime.getRuntime().addShutdownHook(Thread). When the JVM shuts down, the hook runs the application’s doClose() method.
public void registerShutdownHook() {
if (this.shutdownHook == null) {
this.shutdownHook = new Thread() {
@Override
public void run() {
doClose();
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}Spring context close logic (Spring 3.2.12)
During shutdown Spring publishes a ContextClosedEvent, stops lifecycle beans via LifecycleProcessor.onClose(), destroys singleton beans, closes the bean factory and marks the context inactive.
protected void doClose() {
boolean actuallyClose;
synchronized (this.activeMonitor) {
actuallyClose = this.active && !this.closed;
this.closed = true;
}
if (actuallyClose) {
logger.info("Closing " + this);
LiveBeansView.unregisterApplicationContext(this);
try {
publishEvent(new ContextClosedEvent(this));
} catch (Throwable ex) {
logger.warn("Exception from ContextClosedEvent listener", ex);
}
try {
getLifecycleProcessor().onClose();
} catch (Throwable ex) {
logger.warn("Exception from LifecycleProcessor on close", ex);
}
destroyBeans();
closeBeanFactory();
onClose();
synchronized (this.activeMonitor) {
this.active = false;
}
}
}Spring Boot actuator shutdown endpoint
Spring Boot’s spring-boot-starter-actuator exposes a /shutdown POST endpoint that triggers the same shutdown sequence. The endpoint can be enabled and secured via configuration.
@ConfigurationProperties(prefix = "endpoints.shutdown")
public class ShutdownMvcEndpoint extends EndpointMvcAdapter {
@PostMapping(produces = {"application/vnd.spring-boot.actuator.v1+json",
"application/json"})
@ResponseBody
public Object invoke() {
return !this.getDelegate().isEnabled()
? new ResponseEntity(Collections.singletonMap("message",
"This endpoint is disabled"), HttpStatus.NOT_FOUND)
: super.invoke();
}
}
public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>>
implements ApplicationContextAware {
private static final Map<String, Object> SHUTDOWN_MESSAGE =
Collections.unmodifiableMap(Collections.singletonMap("message",
"Shutting down, bye..."));
private ConfigurableApplicationContext context;
@Override
public Map<String, Object> invoke() {
if (this.context == null) {
return Collections.singletonMap("message", "No context to shutdown.");
}
Thread thread = new Thread(() -> {
try { Thread.sleep(500L); } catch (InterruptedException ignored) {}
this.context.close();
});
thread.setContextClassLoader(this.getClass().getClassLoader());
thread.start();
return SHUTDOWN_MESSAGE;
}
}Typical configuration (application.properties):
# Enable the shutdown endpoint
endpoints.shutdown.enabled=true
# Optional: make it unsecured
endpoints.shutdown.sensitive=false
# Management server (optional)
management.context-path=/manage
management.port=8088
management.address=127.0.0.1Summary of the shutdown flow
OS sends SIGTERM (or the actuator endpoint invokes ApplicationContext.close()).
The JVM’s SignalHandler translates the signal into Shutdown.exit().
Registered shutdown hooks run; Spring’s hook calls doClose().
Spring publishes ContextClosedEvent, stops lifecycle beans, destroys singletons, and closes the bean factory.
The process finally exits.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn 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.
