Spring Boot graceful shutdown
Graceful shutdown support was added in Spring Boot 2.3 (release in May, 2020). This allows active requests to complete before closing the context, and shutting down container.
When graceful shutdown is enabled, application will perform following steps sequentially upon shutdown:
- stop accepting new requests
- will wait for some configurable time to process already accepted requests
- stop container
- stop embedded server
From release notes:
Graceful shutdown is supported with all four embedded web servers (Jetty, Reactor Netty, Tomcat, and Undertow) and with both reactive and Servlet-based web applications. When enabled using
server.shutdown=graceful
, upon shutdown, the web server will no longer permit new requests and will wait for a grace period for active requests to complete. The grace period can be configured usingspring.lifecycle.timeout-per-shutdown-phase
.
- To enable graceful shutdown, add
server.shutdown=graceful
to properties (by default it is set toimmediate
). - Grace period can be configured using
spring.lifecycle.timeout-per-shutdown-phase
property (example:spring.lifecycle.timeout-per-shutdown-phase=1m
.
For Spring Boot < 2.3, you'll take to tinker with server's connector to stop accepting new requests as explained in this Spring GitHub issue.
I have ended up with:
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.catalina.connector.Connector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
public class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
private volatile Connector connector;
@Override
public void customize(Connector connector) {
this.connector = connector;
}
@Override
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
log.info("Protocol handler is shutting down");
this.connector.pause();
Executor executor = this.connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
try {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(30, TimeUnit.SECONDS))
log.warn("Tomcat thread pool did not shut down gracefully within 30 seconds. Proceeding with forceful shutdown");
else
log.info("Protocol handler shut down");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
some more additional beans:
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
...
@Bean
public GracefulShutdown gracefulShutdown() {
return new GracefulShutdown();
}
@Bean
public EmbeddedServletContainerFactory servletContainer(final GracefulShutdown gracefulShutdown) {
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
factory.addConnectorCustomizers(gracefulShutdown);
return factory;
}
...