javaspring-bootspring-boot-autoconfiguration

How to dynamically find an available port in Spring Boot when the configured port is already in use?


I'm building a Spring Boot starter and want it to automatically try ports sequentially (8080, 8081, etc.) when the default port is already in use, instead of crashing.

I know server.port=0 selects a random port, but this is inconvenient for development.

My current code logs the server URL on startup:


@Bean

public ApplicationListener<ApplicationReadyEvent> logWhenReadyEvent(LoggingProperties properties, Environment environment) {

    return event -> {

        String port = environment.getProperty("server.port", "8080");

        // Log server URL

    };

}

Tried to do it this way


@Component

public class PortFinder implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {

    @Override

    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {

        // Try to set available port - doesn't work

    }

}

Thanks for the suggestion. I need to catch the BindException Add it as a bean. My applications are now autoconfigured to change their ports dynamically.

I don't have to put the server.port every time! Thanks! credits

Working solution

@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer() {
    return new SpringPortSolver();
}

public static class SpringPortSolver implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
    private static final Logger logger = LoggerFactory.getLogger(SpringPortSolver.class);
    
    @Value("${server.port:8080}")
    String serverPort;
    
    @Override
    public void customize(ConfigurableWebServerFactory factory) {
        int port;
        try {
            port = Integer.parseInt(serverPort);
        } catch (NumberFormatException e) {
            logger.error("Invalid server.port value: {}. Using default port 8080 instead.", serverPort);
            port = 8080;
        }
        
        int originalPort = port;
        int maxPortToTry = port + 100; // Limit how far we'll search to avoid infinite loops
        
        logger.info("Attempting to find available port starting from {}", port);
        
        while (port < maxPortToTry) {
            try (ServerSocket socket = new ServerSocket()) {
                // Use bind with InetSocketAddress to better handle binding issues
                socket.setReuseAddress(true);
                socket.bind(new InetSocketAddress("localhost", port));
                
                int availablePort = socket.getLocalPort();
                logger.info("Port {} is available. Using port {} for server.", availablePort, availablePort);
                factory.setPort(availablePort);
                return;
            } catch (BindException e) {
                logger.warn("Port {} is already in use. Trying port {} instead.", port, port + 1);
                port += 1;
            } catch (IOException e) {
                logger.error("Failed to test port {}: {}", port, e.getMessage());
                // Try next port anyway
                port += 1;
            }
        }
        
        // If we've exhausted all ports in our range, use a random port
        logger.warn("Unable to find available port in range {}-{}. Using random port instead.", originalPort, maxPortToTry);
        factory.setPort(0);
    }
}

Log


Solution

  • In SpringBoot in order to customize the default behavior of embedded Tomcat, you need to implement WebServerFactoryCustomizer interface and override the property as per your needs. Following steps should work for you.

    Here we are starting from port 8080 & trying port till 8095(Increase as you wish), and as soon as we find a free port we break the while loop and set the Port for WebServer.

    @SpringBootApplication
    public class SpringEcaApplication implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
        public static void main(String[] args) {
            SpringApplication.run(SpringEcaApplication.class, args);
        }
    
        @Override
        public void customize(ConfigurableWebServerFactory factory) {
            int serverPorts = 8080;
            int range = 8095;  
            int availablePort = 0;
            while (serverPorts < range) {
                try (ServerSocket socket = new ServerSocket(serverPorts)) { // checks if port is available
                    availablePort = serverPorts;  // available port break the loop
                    break;
                } catch (IOException e) {
                    serverPorts++;  //  this is executed if port is already bind.
                }
            }
            factory.setPort(availablePort);  // Sets the available port
        }
    }