Zero‑Downtime Spring Boot Deployment: Sharing a Single Port Across Versions

This article explains how to update a Spring Boot application without stopping the service by allowing two processes to share the same port, detailing the underlying Tomcat embedding, DispatcherServlet handling, and providing step‑by‑step code and a live demo to achieve seamless zero‑downtime deployments.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Zero‑Downtime Spring Boot Deployment: Sharing a Single Port Across Versions

Problem

When updating a Spring Boot application on a personal or enterprise server, the usual practice is to stop the existing process before starting a new one because both processes need the same TCP port. This results in a brief outage, which is especially problematic for monolithic applications serving many concurrent users.

Design Idea

The approach leverages Spring Boot’s embedded Tomcat and the servlet container lifecycle to start a new instance on an alternative port while the old instance continues to serve traffic. After the new instance is fully started, the old process is terminated, the original port is reclaimed, and a new web server bound to the original port is started, achieving a seamless switch with near‑zero downtime.

Key Technical Steps

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

If the port is occupied, launch the new Spring Boot instance on a temporary port (e.g., 9090) while keeping the old instance running.

When the new instance reports that it is ready, kill the old process (using lsof / kill on Unix‑like systems).

Reconfigure the TomcatServletWebServerFactory to use the original port, obtain a new WebServer from the factory, and start it.

Stop the web server that was created for the temporary port, completing the hand‑over.

Implementation

import org.apache.catalina.startup.Tomcat;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;

import java.io.IOException;
import java.net.ServerSocket;
import java.lang.reflect.Method;
import java.util.Collection;

@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)) {
                    // wait until the old process releases the port
                }
                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) {
                // handle errors as needed
            }
        }
    }

    private static boolean isPortInUse(int port) {
        try (ServerSocket ss = new ServerSocket(port)) {
            return false;
        } catch (IOException e) {
            return true;
        }
    }

    private static ServletContextInitializer invokeSelfInitialize(ServletWebServerApplicationContext ctx) {
        try {
            Method m = ServletWebServerApplicationContext.class.getDeclaredMethod("getSelfInitializer");
            m.setAccessible(true);
            return (ServletContextInitializer) m.invoke(ctx);
        } catch (Throwable t) {
            throw new RuntimeException(t);
        }
    }

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

Demo and Verification

A simple controller returning "1" is packaged as v1.jar and started. A second version returning "2" is built as v2.jar. The first version runs on the default port. When v2.jar is launched, the application detects that port 8088 is occupied, starts on 9090, and once ready the script kills the old process, switches the new instance back to 8088, and stops the temporary server. The whole hand‑over takes less than one second, resulting in negligible downtime.

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.

deploymenttomcatspring-bootport-switchingzero-downtime
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

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.