How to Gracefully Shut Down a Spring Boot Application: Kill Signals, Hooks, and Thread Pools

This article explains the differences between kill -9 and kill -15 signals for Spring Boot, demonstrates how to use shutdown hooks, the Actuator endpoint, and proper thread‑pool termination to achieve an orderly application shutdown without data loss or resource leaks.

Programmer DD
Programmer DD
Programmer DD
How to Gracefully Shut Down a Spring Boot Application: Kill Signals, Hooks, and Thread Pools

When managing Spring Boot applications, many operators use kill -9 <pid> to restart the embedded Tomcat, but this forceful approach bypasses graceful shutdown logic.

kill -9 and kill -15: what’s the difference?

Traditional WAR deployments are stopped via scripts, but Spring Boot packages the server with the application, raising the question of how to stop it cleanly. The kill command can send signals; kill -l lists them. Signal 9 (KILL) terminates a process immediately, while signal 15 (TERM) notifies the process to shut down voluntarily.

kill -l
HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH POLL PWR SYS

The article provides a simple Spring Boot example with a DisposableBean implementation and a JVM shutdown hook that logs messages during shutdown.

@Component
public class TestDisposableBean implements DisposableBean {
    @Override
    public void destroy() throws Exception {
        System.out.println("Test Bean destroyed ...");
    }
}

@SpringBootApplication
@RestController
public class TestShutdownApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestShutdownApplication.class, args);
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("Executing ShutdownHook ...");
        }));
    }
}

Testing steps:

Run java -jar test-shutdown-1.0.jar to start the application.

Issue kill -9 pid, kill -15 pid, or Ctrl+C and observe the logs.

Results show that kill -15 pid (or Ctrl+C) triggers the shutdown hook, logs the closing of the AnnotationConfigEmbeddedWebApplicationContext, executes the hook, and calls DisposableBean#destroy(). In contrast, kill -9 pid kills the process without any application logs.

Therefore, kill -15 pid allows the application to perform cleanup such as closing sockets, removing temporary files, notifying subscribers, and releasing resources, while kill -9 pid simulates a sudden crash.

How Spring Boot Handles the TERM Signal

When the JVM receives the TERM signal, the registered shutdown hook invokes ApplicationContext#close(), which calls doClose(). This method publishes a ContextClosedEvent, stops lifecycle beans, destroys cached singletons, closes the bean factory, and finally shuts down the embedded Tomcat.

@Override
public void registerShutdownHook() {
    if (this.shutdownHook == null) {
        this.shutdownHook = new Thread() {
            @Override
            public void run() {
                synchronized (startupShutdownMonitor) {
                    doClose();
                }
            }
        };
        Runtime.getRuntime().addShutdownHook(this.shutdownHook);
    }
}

@Override
public void close() {
    synchronized (this.startupShutdownMonitor) {
        doClose();
        if (this.shutdownHook != null) {
            Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
        }
    }
}

Understanding this flow highlights why graceful shutdown is essential.

Other Graceful Shutdown Options

Spring Boot Actuator provides a /shutdown endpoint. Adding the dependency and enabling the endpoint allows a POST request to trigger a graceful shutdown.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

# Enable shutdown
endpoints.shutdown.enabled=true
# Disable authentication (use with caution)
endpoints.shutdown.sensitive=false

Calling curl -X POST host:port/shutdown returns {"message":"Shutting down, bye..."}. In production, secure this endpoint with Spring Security.

Gracefully Destroying Thread Pools

Thread pools must be shut down during application termination. Implementing DisposableBean allows explicit shutdown:

@Service
public class SomeService implements DisposableBean {
    ExecutorService executorService = Executors.newFixedThreadPool(10);

    public void concurrentExecute() {
        executorService.execute(() -> System.out.println("executed..."));
    }

    @Override
    public void destroy() throws Exception {
        executorService.shutdownNow(); // or shutdown() with awaitTermination
    }
}

Understanding the difference between shutdown() (orderly) and shutdownNow() (attempts immediate stop) is crucial. Both require awaitTermination to ensure tasks finish or are cancelled.

/**
 * Perform a shutdown on the underlying ExecutorService.
 */
public void shutdown() {
    if (this.waitForTasksToCompleteOnShutdown) {
        this.executor.shutdown();
    } else {
        this.executor.shutdownNow();
    }
    awaitTerminationIfNecessary();
}

private void awaitTerminationIfNecessary() {
    if (this.awaitTerminationSeconds > 0) {
        try {
            this.executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS);
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
        }
    }
}

Additional strategies for graceful shutdown include traffic draining, using ACID transactions, ACK mechanisms in messaging, and idempotent designs for scheduled tasks.

When the virtual machine begins its shutdown sequence it will start all registered shutdown hooks in some unspecified order and let them run concurrently. When all the hooks have finished it will then run all uninvoked finalizers if finalization‑on‑exit has been enabled. Finally, the virtual machine will halt.

Shutdown hooks keep the JVM alive until they complete, allowing blocked operations to finish before the process exits.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

thread poolshutdown hookspring-bootKill Signal
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.