Terminating mvn spring-bootrun doesn't stop tomcat
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
If mvn spring-boot:run appears to stop but port 8080 is still occupied, the usual explanation is not that embedded Tomcat turned into an independent server. More often, the JVM is still alive because another thread, child process, or second Java process is holding the application open. The fix starts with understanding what spring-boot:run actually launches and what must shut down for the process to exit cleanly.
What Should Happen on Ctrl+C
spring-boot:run starts your application inside a JVM launched by Maven. When you press Ctrl+C, the terminal sends an interrupt signal to that process. Spring Boot then closes the application context, and embedded Tomcat should stop as part of normal shutdown.
So if the server keeps responding after the terminal command ends, one of two things is usually true:
- the same JVM never exited
- a different Java process is listening on the same port
That distinction matters because debugging the wrong process wastes time.
First Confirm What Is Still Running
Before changing application code, identify the process that still owns the port:
If the PID still belongs to the Spring Boot app, the JVM did not exit. If the PID belongs to another process, then Tomcat from spring-boot:run is not the problem at all. You may have a previous instance, an IDE-launched run configuration, or a containerized service bound to the same port.
Why the JVM Stays Alive
Embedded Tomcat usually shuts down correctly when the Spring context closes. The common blockers are elsewhere:
- non-daemon threads started manually
- executors that are never shut down
- external child processes launched by the app
- shutdown hooks that block forever
- development tooling that started a separate process
In other words, "Tomcat does not stop" is often shorthand for "the whole application never terminated."
Shut Down Your Own Background Work
If your application creates threads or executors, tie their lifecycle to Spring so they stop when the application stops:
The key point is not the exact API. The key point is that anything you start must also have a well-defined stop path. Without that, the JVM may stay alive after Maven receives Ctrl+C.
Keep the Problem Reproducible
A useful test is to run the jar directly instead of through Maven:
If shutdown works here but not with spring-boot:run, the difference may come from your development setup, shell, or Maven integration. If shutdown fails in both cases, the problem is more likely inside the application lifecycle itself.
Check for Duplicate Launch Paths
It is also common to start the application in one place and stop it in another. Examples include:
- an IDE run configuration started the app earlier
- Docker Compose is still publishing the same port
- a second terminal launched another
java -jar - a file watcher or restart tool spawned a replacement process
That is why process inspection should come before code changes.
Common Pitfalls
- Assuming embedded Tomcat detached from Maven when the real issue is a JVM that never exited.
- Forgetting to stop custom thread pools, schedulers, or long-running consumers.
- Debugging
spring-boot:runbefore checking whether another Java process owns the port. - Treating any port conflict as a Tomcat shutdown bug without confirming the PID.
- Adding force-kill commands before fixing the application lifecycle that caused the hang.
Summary
- '
Ctrl+Cshould stop the Spring Boot JVM and embedded Tomcat together.' - If the server still appears alive, first identify which process still owns the port.
- In many cases the blocker is a non-daemon thread, executor, or child process, not Tomcat itself.
- Tie background work to Spring shutdown using lifecycle callbacks such as
@PreDestroy. - Reproduce the issue with
java -jaras a quick way to separate Maven behavior from application behavior.

