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
};
}
@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
@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);
}
}
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
}
}