Mastering Graceful Shutdown and Startup for Spring Boot Microservices
Learn how to implement elegant service shutdown and startup in microservice architectures using Java's shutdown hooks, Spring Boot's built-in mechanisms, Docker container signals, and external containers like Jetty, with code examples and best‑practice strategies to avoid traffic hitting unhealthy instances.
For microservices, graceful service up‑ and down‑times are essential. A service should not be exposed before its component or container starts successfully, and it must be taken offline when the host machine stops, preventing upstream traffic from reaching unhealthy instances.
1 Graceful Shutdown
Basic shutdown (Spring/SpringBoot/built‑in container)
The JVM supports graceful termination via shutdownHook.
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
close();
}
});This approach works for the following scenarios:
1) Normal program exit 2) Calling System.exit() 3) Pressing Ctrl+C in the terminal 4) Killing the process with kill pid
Forcibly killing with kill -9 cannot be handled gracefully.
Spring Boot already registers a shutdown hook that reacts to Ctrl+C or the SIGTERM (signal 15) signal.
When the application receives the signal, Spring prints a "Closing..." log in AnnotationConfigEmbeddedWebApplicationContext, whose parent class AbstractApplicationContext implements the actual shutdown logic.
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)) {
if (this.logger.isInfoEnabled()) {
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);
}
}Since doClose() publishes a ContextClosedEvent, we can create a listener to perform service deregistration from the registry when the event occurs.
@Component
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
// deregistration logic
zookeeperRegistry.unregister(mCurrentServiceURL);
...
}
}After deregistering, traffic should be blocked using the framework's fault‑tolerance mechanisms.
Docker shutdown
Docker stop sends SIGTERM to PID 1 and waits 10 seconds before sending SIGKILL. The application can handle the SIGTERM to perform graceful shutdown. The timeout can be adjusted with docker stop -t.
External container shutdown (Jetty)
When using an external container, graceful shutdown can be achieved by:
Calling the RPC framework’s graceful‑down interface, which is wrapped in a preStop script executed before the container stops.
Simply adding a kill -15 command to the shutdown script.
2 Graceful Startup
Graceful startup is trickier because there is no default implementation. The principle is to expose the service only after the listening port is ready.
Spring Boot built‑in container startup
Spring Boot can perform health checks before exposing the service. For example, SOFA‑Boot runs 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();
}Because ContextRefreshedEvent may fire multiple times, a more reliable event is ApplicationReadyEvent, which is emitted after the container has fully started and the port is bound.
@Component
public class GracefulStartupListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// registration logic – graceful startup
apiRegister.register(urls);
...
}
}External container (Jetty) startup
For external containers, the application cannot directly observe the container’s state. The solution is to let the RPC framework provide a graceful‑up interface, wrap the call in a postStart script, and execute it after the container starts, following the sequence: start container → health check → register service → health‑up check.
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.
Java Interview Crash Guide
Dedicated to sharing Java interview Q&A; follow and reply "java" to receive a free premium Java interview guide.
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.
