Allocating available random Ports in a Maven Build
May 7th, 2014 by Micha KopsRecently in a project I encountered the following problem: The development team used Git with a branch-per-feature-like workflow and the integration server, Bamboo in this case, was configured not only to run the integration-tests for the master-branch but also for every change in a feature branch.
As the team developed a Java EE web application ports like 8080 occasionally were already bound and builds failed.
I knew a plug-in for Jenkins CI I to search for available ports and assign them to a build variable but I wanted to control such information directly within the Maven build life-cycle so I searched and finally found Sonatype’s Port Allocator Plug-in for Maven.
In the following short example I’m going to demonstrate how to allocate available random ports in a Maven build and assign them to an embedded servlet container.
Contents
How does work?
We’re simply programming a web application that does nothing more than to print the port used to the browser.
The web applications runs on an embedded Jetty server that we’re setting up programmatically using a port that is passed into our application as parameter.
Then it is time for the Maven-plugin magic to take place: The port-allocator-plugin does what it’s name suggests and determines a random, available port and writes it to a variable that is now available in the Maven life-cycle.
Finally we’re using the Maven Exec Plugin to run our Java class with the web-server and with the random port from the Maven variable as parameter.
Let’s advance to the different steps in detail…
Maven Dependecies
We may ignore the dependencies for Jetty here, it’s just needed to write the servlet and configure the embedded servlet container instance.
The interesting part is the port-allocator-plugin configuration: Here we configure the plugin to detect an available random port and write the port number to a Maven variable named tomcat-port.
Now that we’ve got the port information stored in a variable we’re free to use this information anywhere in our Maven build – in this case, we’re passing the variable as a run-time argument for the Maven Exec Plugin.
Otherwise we also might have used the variable with Maven’s resource filtering capabilities e.g. to write the value into a config file, use the Maven Templating Plugin to modify the source code directly (see my article: “Filtering Source Files using the Templating Maven Plugin“) or set this variable as a system property – the possibilities are endless.
<dependencies> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> <version>9.0.0.RC2</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> <version>9.0.0.RC2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.sonatype.plugins</groupId> <artifactId>port-allocator-maven-plugin</artifactId> <version>1.2</version> <executions> <execution> <phase>validate</phase> <goals> <goal>allocate-ports</goal> </goals> <configuration> <ports> <port> <name>tomcat-port</name> </port> </ports> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.3</version> <executions> <execution> <goals> <goal>exec</goal> </goals> </execution> </executions> <configuration> <arguments> <argument>${tomcat-port}</argument> </arguments> </configuration> </plugin> </plugins> </build>
Simple Web Application
Now we’re ready to write our web application that fulfils these two requirements: It starts a servlet container running on a port passed as a parameter and it runs a servlet that write some output to the browser.
Servlet Printing the Port
The following simple servlet does nothing more than to print the port number to the browser window.
package com.hascode.tutorial; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class DemoServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse res) throws ServletException, IOException { res.getWriter() .append("I'm a happy web server, running on port " + req.getLocalPort()); } }
Web Server
Our web server consists of one single Java class with the static main method that takes the port number as parameter and initializes an embedded Jetty server listening on this port.
If you’re interesting in embedded servlet container in detail, please feel free to have a look at my blog article “Embedding Jetty or Tomcat in your Java Application“.
package com.hascode.tutorial; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; public class HttpRunner { public static void main(final String[] args) throws Exception { int port = Integer.parseInt(args[0]); System.out.println("Starting embedded Jetty with port: " + port); Server server = new Server(); ServerConnector c = new ServerConnector(server); c.setIdleTimeout(1000); c.setAcceptQueueSize(10); c.setPort(port); c.setHost("localhost"); ServletContextHandler handler = new ServletContextHandler(server, "/app", true, false); ServletHolder servletHolder = new ServletHolder(DemoServlet.class); handler.addServlet(servletHolder, "/test"); server.addConnector(c); server.start(); } }
Running Multiple Instances
We’re starting two instances now using the Maven Exec Plugin – and as you can see in the following log excerpt – one instance is running on port 42449 , the other one on port 43685.
soma@styx /data/project/maven-port-allocator-tutorial $ mvn compile exec:java -Dexec.mainClass=com.hascode.tutorial.HttpRunner & [1] 4743 soma@styx /data/project/maven-port-allocator-tutorial $ [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building port-allocator-example 1.0.0 [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- port-allocator-maven-plugin:1.2:allocate-ports (default) @ port-allocator-example --- [INFO] Assigning port '42449' to property 'tomcat-port' [INFO] [INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ port-allocator-example --- [debug] execute contextualize [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] Copying 0 resource [INFO] [INFO] --- maven-compiler-plugin:2.4:compile (default-compile) @ port-allocator-example --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- exec-maven-plugin:1.3:java (default-cli) @ port-allocator-example --- [WARNING] Warning: killAfter is now deprecated. Do you need it ? Please comment on MEXEC-6. Starting embedded Jetty with port: 42449 2014-05-06 22:11:00.377:INFO:oejs.Server:com.hascode.tutorial.HttpRunner.main(): jetty-9.0.0.RC2 2014-05-06 22:11:00.412:INFO:oejsh.ContextHandler:com.hascode.tutorial.HttpRunner.main(): started o.e.j.s.ServletContextHandler@25d480f6{/app,null,AVAILABLE} 2014-05-06 22:11:00.424:INFO:oejs.ServerConnector:com.hascode.tutorial.HttpRunner.main(): Started ServerConnector@2d6df1c8{HTTP/1.1}{localhost:42449} mvn compile exec:java -Dexec.mainClass=com.hascode.tutorial.HttpRunner [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------------------ [INFO] Building port-allocator-example 1.0.0 [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- port-allocator-maven-plugin:1.2:allocate-ports (default) @ port-allocator-example --- [INFO] Assigning port '43685' to property 'tomcat-port' [INFO] [INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ port-allocator-example --- [debug] execute contextualize [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] Copying 0 resource [INFO] [INFO] --- maven-compiler-plugin:2.4:compile (default-compile) @ port-allocator-example --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- exec-maven-plugin:1.3:java (default-cli) @ port-allocator-example --- [WARNING] Warning: killAfter is now deprecated. Do you need it ? Please comment on MEXEC-6. Starting embedded Jetty with port: 43685 2014-05-06 22:11:07.221:INFO:oejs.Server:com.hascode.tutorial.HttpRunner.main(): jetty-9.0.0.RC2 2014-05-06 22:11:07.251:INFO:oejsh.ContextHandler:com.hascode.tutorial.HttpRunner.main(): started o.e.j.s.ServletContextHandler@4de2ab6d{/app,null,AVAILABLE} 2014-05-06 22:11:07.262:INFO:oejs.ServerConnector:com.hascode.tutorial.HttpRunner.main(): Started ServerConnector@61726a5c{HTTP/1.1}{localhost:43685}
We’re now able to open the application in our browser using the URL: http://localhost:RANDOMPORT/app/test
This is what the application looks like in our browser:
Tutorial Sources
Please feel free to download the tutorial sources from my Bitbucket repository, fork it there or clone it using Git:
git clone https://bitbucket.org/hascode/maven-port-allocation-tutorial.git
July 10th, 2017 at 2:16 pm
Hi,
I don’t understand why the content of “tomcat-port” variable ends up in args[0] of the main method of HttpRunner, why not in args[1]? where is the link?
July 10th, 2017 at 5:37 pm
Hi Alex,
in the Maven lifecycle during the validation phase, the port-allocator-maven-plugin allocates a new random port and puts it into the Maven property named “tomcat-port”.
When we’re executing the command “mvn exec:java -Dexec.mainClass=com.hascode.tutorial.HttpRunner”, the configuration for the exec-maven-plugin from the pom.xml is applied and as it is bound to the exec-phase, the property “tomcat-port” is added as first argument to the command.
Finally that’s why the random-port is passed as first argument (args[0]) to our main method.
if you want to avoid the parameter approach, you could also use resource filtering and add the port-property to a property-file or configuration file.
Please don’t hesitate to ask if there are further questions!
Cheers,
Micha