Mastering Graceful Shutdown and Startup for Spring Boot Microservices
This article explains how to implement elegant service shutdown and startup in microservice architectures using JVM shutdown hooks, Spring Boot lifecycle events, Docker signal handling, and external container scripts, ensuring traffic is safely managed and services are registered or deregistered correctly.
For microservices, graceful service up‑and‑down is essential; a service should not be exposed before it is ready and must be fully deregistered before the host stops.
Graceful Shutdown
Basic shutdown (Spring/SpringBoot/built‑in container)
The JVM supports graceful termination via a shutdown hook.
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
close();
}
});This method works in the following scenarios:
Normal program exit
Calling System.exit() Pressing Ctrl+C in the terminal
Killing the process with kill pid Using kill -9 leaves the program helpless.
Spring Boot already registers a shutdown hook that reacts to Ctrl+C or SIGTERM (kill -15). The implementation resides in AnnotationConfigEmbeddedWebApplicationContext and its parent AbstractApplicationContext, which publishes a ContextClosedEvent during doClose().
public void registerShutdownHook() {
if (this.shutdownHook == null) {
this.shutdownHook = new Thread() {
public void run() {
synchronized (AbstractApplicationContext.this.startupShutdownMonitor) {
AbstractApplicationContext.this.doClose();
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
public void destroy() {
this.close();
}
public void close() {
synchronized (this.startupShutdownMonitor) {
this.doClose();
if (this.shutdownHook != null) {
try { Runtime.getRuntime().removeShutdownHook(this.shutdownHook); } catch (IllegalStateException ignored) {}
}
}
}
protected void doClose() {
if (this.active.get() && this.closed.compareAndSet(false, true)) {
this.logger.info("Closing " + this);
LiveBeansView.unregisterApplicationContext(this);
try { this.publishEvent(new ContextClosedEvent(this)); } catch (Throwable ex) { this.logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex); }
if (this.lifecycleProcessor != null) {
try { this.lifecycleProcessor.onClose(); } catch (Throwable ex) { this.logger.warn("Exception thrown from LifecycleProcessor on context close", ex); }
}
this.destroyBeans();
this.closeBeanFactory();
this.onClose();
this.active.set(false);
}
}Developers can listen to ContextClosedEvent to deregister the service from a registry:
@Component
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
// deregistration logic
zookeeperRegistry.unregister(mCurrentServiceURL);
...
}
}After deregistration, incoming traffic should be blocked using the framework’s fault‑tolerance mechanisms.
Docker Shutdown
Docker’s stop sends SIGTERM to PID 1, waits the default 10 seconds, then sends SIGKILL. Programs should handle SIGTERM to perform graceful shutdown; the timeout can be adjusted with docker stop -t.
External Container Shutdown Script (Jetty)
When using an external container, graceful shutdown can be achieved either by invoking the RPC framework’s shutdown interface in a pre‑stop script or by simply issuing kill -15 in the script.
Graceful Startup
Graceful startup is more challenging because there is no default implementation; the key principle is to expose the service only after the port is ready.
SpringBoot Built‑in Container Graceful Startup
Industry practice uses health checks before exposing the service. For example, SOFA‑Boot performs component health checks, then middleware checks, and only after all checks pass does it register the service.
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
healthCheckerProcessor.init();
healthIndicatorProcessor.init();
afterHealthCheckCallbackProcessor.init();
publishBeforeHealthCheckEvent();
readinessHealthCheck();
}The ContextRefreshedEvent is fired after the embedded container starts, guaranteeing the port is available. However, this event may fire multiple times in some scenarios.
A more reliable event is ApplicationReadyEvent, which is published after the container is fully started. Listening to this event allows safe registration of the service:
@Component
public class GracefulStartupListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// registration logic for graceful startup
apiRegister.register(urls);
...
}
}External Container (Jetty) Graceful Startup
For external containers, the RPC framework must provide a graceful startup interface that is invoked in a post‑start script after health checks succeed.
Typical flow: start container → health check → startup logic → service becomes healthy.
Source: fredalxin Link: https://fredal.xin/graceful-soa-updown
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
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.
