Why kill -9 vs kill -15? Mastering Graceful Shutdown in Spring Boot

This article explains the difference between kill -9 and kill -15 signals, how Spring Boot processes the TERM signal, and provides practical code examples for implementing graceful shutdown, including shutdown hooks, DisposableBean, and proper thread‑pool termination strategies.

Programmer DD
Programmer DD
Programmer DD
Why kill -9 vs kill -15? Mastering Graceful Shutdown in Spring Boot

After a period of not updating the blog, the author reflects on writing practices and the shift from copying tutorials to relying on source code debugging and official documentation.

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

Traditionally, web applications were stopped by running startup/shutdown scripts on a deployed WAR in a servlet container. With Spring Boot’s embedded Tomcat, many simply kill the process using kill -9 <pid>, but the consequences are rarely analyzed.

In Linux, the kill command sends a signal identified by a number. Signal 9 (KILL) forces the kernel to terminate the process immediately, while signal 15 (TERM) politely asks the process to shut down. kill -l The article focuses on signal 9 (KILL) and signal 15 (TERM).

Gracefully stopping a Spring Boot application with kill -15

To demonstrate, a simple Spring Boot demo is created. First, a class implementing DisposableBean is added:

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

Next, a JVM shutdown hook is registered:

@SpringBootApplication
@RestController
public class TestShutdownApplication implements DisposableBean {
    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 app.

Execute kill -9 <pid>, kill -15 <pid> or Ctrl+C and observe the logs.

Results show that kill -15 and Ctrl+C trigger the shutdown hook, allowing the DisposableBean#destroy() method to run, while kill -9 kills the process without any logs.

Typical cleanup actions during shutdown include closing socket connections, cleaning temporary files, notifying subscribers, informing child processes, and releasing resources.

How Spring Boot handles the TERM signal

When the JVM receives SIGTERM, the registered shutdown hook calls close() on the application context. The core logic resides in AbstractApplicationContext.registerShutdownHook() and ultimately in AbstractApplicationContext.doClose(), which performs:

Unregistering the application context from LiveBeansView.

Publishing a ContextClosedEvent.

Stopping all lifecycle beans.

Destroying singleton beans.

Closing the bean factory.

Executing subclass-specific shutdown logic.

For an embedded web context, additional steps shut down Tomcat/Jetty.

Other graceful shutdown methods

Spring Boot’s spring-boot-starter-actuator provides a REST endpoint for graceful shutdown. Adding the dependency and enabling the endpoint in application.properties:

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

# Enable shutdown endpoint
endpoints.shutdown.enabled=true
# Disable authentication for the endpoint
endpoints.shutdown.sensitive=false

Calling curl -X POST http://host:port/shutdown returns {"message":"Shutting down, bye..."}. However, most teams still prefer kill -15 because it achieves the same effect.

Properly shutting down thread pools

Even though the JVM reclaims resources, long‑running asynchronous tasks can cause issues if a thread pool is not closed correctly. A typical service might look like:

@Service
public class SomeService {
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    public void concurrentExecute() {
        executorService.execute(() -> System.out.println("executed..."));
    }
}

Implementing DisposableBean allows graceful termination:

@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() followed by awaitTermination
    }
}

The difference between shutdown() (orderly, waits for running tasks) and shutdownNow() (attempts to interrupt running tasks) is highlighted, and both require awaitTermination to ensure completion.

Spring’s built‑in thread‑pool shutdown support

Spring’s ExecutorConfigurationSupport implements DisposableBean and provides a configurable shutdown strategy:

public void destroy() {
    shutdown();
}

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();
        }
    }
}

Configuration flags control whether to wait for tasks to finish and how long to wait.

Additional graceful shutdown considerations

In distributed systems, graceful shutdown often involves traffic isolation (e.g., deregistering from a service registry), using transactional guarantees, ACK mechanisms in messaging, and idempotent designs for scheduled jobs.

Even in crash scenarios like kill -9, features such as ACID transactions and message ACKs help preserve consistency.

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.

A shutdown hook keeps the JVM alive until the hook completes, allowing blocking operations to finish before the process exits.

QR code
QR code
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.

javaSpring Bootthread poolGraceful ShutdownLinux signalsDisposableBean
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.