Zero‑Downtime SpringBoot Updates: Share the Same Port Between Old and New Instances

This article explains how to update a SpringBoot application without stopping the service by letting the old and new processes share the same port, detailing the underlying Tomcat and ServletContainerInitializer mechanisms, providing a step‑by‑step implementation and a demo for seamless zero‑downtime deployments.

Top Architect
Top Architect
Top Architect
Zero‑Downtime SpringBoot Updates: Share the Same Port Between Old and New Instances

Background and Problem

When updating code on a personal or enterprise server, the usual approach is to stop the existing process because the new process needs the same port, leading to temporary service unavailability. This is especially problematic for monolithic SpringBoot applications with many concurrent users.

Simple Workaround

A common solution is to start the new version on a different port, update the Nginx proxy to point to the new port (Nginx reloads quickly), and then terminate the old process. While effective, it requires manual port switching and scripting.

Advanced Solution: Shared Port Technique

The article introduces a technique that allows two SpringBoot processes to truly share the same port, eliminating the need for manual port changes. The core idea relies on Tomcat’s embedded servlet container and the ServletContainerInitializer protocol.

Design Overview

Understand the embedded Servlet container in SpringBoot.

Determine how DispatcherServlet is passed to the servlet container.

Tomcat provides the class org.apache.catalina.startup.Tomcat. Creating a Tomcat instance is as simple as new Tomcat() followed by start(). SpringBoot uses TomcatServletWebServerFactory to create the Tomcat container and registers a ServletContextInitializer implementation ( TomcatStarter) that ultimately adds the DispatcherServlet to Tomcat via the ServletContainerInitializer callback.

Key Implementation Steps

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

If occupied, start the new application on an alternative port (e.g., 9090) and set a flag to indicate a port change.

After the new instance starts, kill the process listening on the original port using lsof and kill -9.

Retrieve the ServletWebServerFactory from the running context, cast it to TomcatServletWebServerFactory, and reset its port to the original value.

Invoke the internal selfInitialize method to obtain the original ServletContextInitializer and start a new WebServer on the original port.

Stop the old web server instance, completing the seamless switch.

Core Code

@SpringBootApplication
@EnableScheduling
public class WebMainApplication {
    public static void main(String[] args) {
        int defaultPort = 8088;
        boolean needChangePort = false;
        String[] newArgs = args.clone();
        if (isPortInUse(defaultPort)) {
            newArgs = new String[args.length + 1];
            System.arraycopy(args, 0, newArgs, 0, args.length);
            newArgs[newArgs.length - 1] = "--server.port=9090";
            needChangePort = true;
        }
        ConfigurableApplicationContext run = SpringApplication.run(WebMainApplication.class, newArgs);
        if (needChangePort) {
            String command = String.format("lsof -i :%d | grep LISTEN | awk '{print $2}' | xargs kill -9", defaultPort);
            try {
                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();
            } catch (IOException | InterruptedException ignored) {}
        }
    }
    private static ServletContextInitializer invokeSelfInitialize(ServletWebServerApplicationContext context) {
        try {
            Method method = ServletWebServerApplicationContext.class.getDeclaredMethod("getSelfInitializer");
            method.setAccessible(true);
            return (ServletContextInitializer) method.invoke(context);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
    private static boolean isPortInUse(int port) {
        try (ServerSocket ss = new ServerSocket(port)) { return false; } catch (IOException e) { return true; }
    }
    private static ServletWebServerFactory getWebServerFactory(ConfigurableApplicationContext ctx) {
        String[] names = ctx.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
        return ctx.getBeanFactory().getBean(names[0], ServletWebServerFactory.class);
    }
}

Testing Procedure

Create a simple controller returning "1", package it as v1.jar, then modify the controller to return "2" and package as v2.jar. Start v1.jar and verify the endpoint works. Without stopping v1, start v2.jar. The shared‑port mechanism will kill the old process, start the new one on the original port, and the service experiences only a sub‑second interruption.

The demo shows the new version taking effect almost instantly, confirming zero‑downtime deployment.

Diagram
Diagram

By leveraging Tomcat’s internal initialization flow and dynamically reassigning the listening port, developers can achieve seamless SpringBoot upgrades without service disruption.

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.

JavaDeploymentSpringBootServletTomcatZeroDowntimePortSharing
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

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.