How to Run Two SpringBoot Instances on the Same Port Seamlessly

When updating a SpringBoot application the old process must be stopped because the new one cannot bind to the same port, but by leveraging Tomcat's embedded container, ServletWebServerFactory, and dynamic port handling you can keep both processes alive and achieve zero‑downtime deployments.

Architect
Architect
Architect
How to Run Two SpringBoot Instances on the Same Port Seamlessly

Introduction

In typical deployment scenarios a SpringBoot application must stop its current process before starting a new version because the new process tries to bind to the same port, causing a port‑conflict error. This leads to a period of unavailability proportional to the startup time of the new instance.

Design Idea

The solution is to let the new SpringBoot process start on an alternative port, then dynamically switch the original port back to the new process without stopping the old one, using Tomcat’s embedded servlet container and SpringBoot’s internal factories.

Technical Background

Tomcat provides the class org.apache.catalina.startup.Tomcat which can be instantiated via new Tomcat() and started with start(). It also offers methods to add servlets and configure connectors.

SpringBoot creates a ServletWebServerFactory (e.g., TomcatServletWebServerFactory) based on the servlet container dependency. The factory can produce a WebServer with start() and stop() methods.

DispatcherServlet is not added directly to Tomcat; instead SpringBoot registers a ServletContainerInitializer implementation ( TomcatStarter) that receives a collection of ServletContextInitializer instances, one of which registers the DispatcherServlet.

The collection of ServletContextInitializer beans can be obtained via ServletContextInitializerBeans, which implements Collection.

Implementation Steps

Check whether the default port (e.g., 8088) is already in use.

If it is, start the new SpringBoot instance on an alternative port (e.g., 9090) by appending --server.port=9090 to the argument list.

After the new instance is up, kill the old process that occupies the default port using a shell command like

lsof -i :8088 | grep LISTEN | awk '{print $2}' | xargs kill -9

.

Retrieve the ServletWebServerFactory from the new application context, cast it to TomcatServletWebServerFactory, set its port back to the default, obtain a new WebServer via getWebServer, and start it.

Finally, stop the temporary web server that was running on the alternative port.

public class WebMainApplication {
    public static void main(String[] args) {
        int defaultPort = 8088;
        boolean needChangePort = false;
        if (isPortInUse(defaultPort)) {
            String[] newArgs = new String[args.length + 1];
            System.arraycopy(args, 0, newArgs, 0, args.length);
            newArgs[newArgs.length - 1] = "--server.port=9090";
            needChangePort = true;
            args = newArgs;
        }
        ConfigurableApplicationContext run = SpringApplication.run(WebMainApplication.class, args);
        if (needChangePort) {
            // kill old process
            String command = String.format("lsof -i :%d | grep LISTEN | awk '{print $2}' | xargs kill -9", defaultPort);
            Runtime.getRuntime().exec(new String[]{"sh", "-c", command}).waitFor();
            while (isPortInUse(defaultPort)) {}
            ServletWebServerFactory factory = getWebServerFactory(run);
            ((TomcatServletWebServerFactory) factory).setPort(defaultPort);
            WebServer webServer = factory.getWebServer(invokeSelfInitialize((ServletWebServerApplicationContext) run));
            webServer.start();
            ((ServletWebServerApplicationContext) run).getWebServer().stop();
        }
    }
    // helper methods omitted for brevity
}

Demo

A simple controller @RestController exposing /port/test/test returns "1". Package it as a JAR (v1) and run it. Then modify the controller to return "2", repackage as v2, and start the new JAR without stopping the v1 process. Using a tool like Cool Request to call the endpoint shows that the service remains available, and after a few seconds the response switches to "2" with virtually no downtime.

Conclusion

The approach demonstrates that by programmatically managing Tomcat’s embedded server and SpringBoot’s internal factories, two SpringBoot processes can share the same port sequentially, enabling seamless, near‑zero‑downtime updates without manual Nginx reconfiguration or complex scripting.

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.

javabackend-developmentSpringBootTomcatPort Sharing
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

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.