Backend Development 8 min read

Zero‑Downtime SpringBoot Port Sharing: Design and Implementation

This article explains how to achieve seamless code updates for SpringBoot applications by allowing two processes to share the same port, detailing the underlying servlet container mechanics, the role of DispatcherServlet, and providing a complete Java implementation with step‑by‑step instructions and code samples.

Architecture Digest
Architecture Digest
Architecture Digest
Zero‑Downtime SpringBoot Port Sharing: Design and Implementation

When updating a SpringBoot application on a server, the usual approach of stopping the old process before starting a new one causes port conflicts and temporary downtime. The article introduces a technique that enables two SpringBoot processes to share the same port, allowing near‑zero‑downtime deployments.

The design relies on understanding the embedded servlet container in SpringBoot. It first examines how SpringBoot creates the embedded Tomcat instance (via TomcatServletWebServerFactory ) and how the DispatcherServlet is registered through the ServletContainerInitializer mechanism, specifically using a TomcatStarter that aggregates ServletContextInitializer beans.

Key steps of the solution are:

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

If occupied, start the new SpringBoot instance on an alternative port (e.g., 9090).

After the new instance is up, kill the old process that holds the original port.

Re‑create a Tomcat server bound to the original port, associate the existing DispatcherServlet , and start it, then stop the temporary server.

Below is a minimal Tomcat‑based example that demonstrates creating a Tomcat instance programmatically:

public class Main {
    public static void main(String[] args) {
        try {
            Tomcat tomcat = new Tomcat();
            tomcat.getConnector();
            tomcat.getHost();
            Context context = tomcat.addContext("/", null);
            tomcat.addServlet("/", "index", new HttpServlet(){
                @Override
                protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                    resp.getWriter().append("hello");
                }
            });
            context.addServletMappingDecoded("/", "index");
            tomcat.init();
            tomcat.start();
        } catch (Exception e) {}
    }
}

The full SpringBoot implementation that performs the zero‑downtime port switch is provided as follows:

@SpringBootApplication()
@EnableScheduling
public class WebMainApplication {
    public static void main(String[] args) {
        String[] newArgs = args.clone();
        int defaultPort = 8088;
        boolean needChangePort = false;
        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 webServerFactory = getWebServerFactory(run);
                ((TomcatServletWebServerFactory) webServerFactory).setPort(defaultPort);
                WebServer webServer = webServerFactory.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 serverSocket = new ServerSocket(port)) {
            return false;
        } catch (IOException e) {
            return true;
        }
    }

    protected static Collection
getServletContextInitializerBeans(ConfigurableApplicationContext context) {
        return new ServletContextInitializerBeans(context.getBeanFactory());
    }

    private static ServletWebServerFactory getWebServerFactory(ConfigurableApplicationContext context) {
        String[] beanNames = context.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
        return context.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
    }
}

A simple test controller is also shown to verify that the service remains available during the switch. Screenshots demonstrate that the old version (v1) continues to serve requests while the new version (v2) is started, and the downtime is reduced to less than one second.

Overall, the article provides a practical, code‑driven method for achieving zero‑downtime deployments in SpringBoot monolithic applications by leveraging Tomcat’s embedded capabilities and Spring’s initialization hooks.

backendJavaSpringBootTomcatZeroDowntimePortSharing
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

0 followers
Reader feedback

How this landed with the community

login 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.