If you're building a Java web app that you yourself or your organization will be deploying then you can save yourself a lot of trouble by avoiding the whole build-to-war + deploy-to-server approach. Instead, you should build your web app as a normal Java application with an embedded web app server. Don't build a WAR, just compile the code and serve the files out of their source location. This has the following advantages:
- You can code and test iteratively because you don't have to copy files and create war packages every time you make a change. This is similar to what the
mvn jetty:run
command is being used for by many developers today. - You run the same exact code in production as you do in development. Hopefully I don't have to elaborate on the advantages of that. Most developers today use
mvn jetty:run
or similar to achieve a quick, iterative dev cycle. But come time for deployment, they build a WAR and throw it over the wall to be deployed in some app server that is managed separately from the application code. This almost always introduces problems. And it doesn't have to be that way.
You can use this maven archetype to get started with the approach:
$ mvn archetype:generate \
-DarchetypeCatalog=http://maven.publicstaticvoidmain.net/archetype-catalog.xml \
-DarchetypeGroupId=net.publicstaticvoidmain \
-DarchetypeArtifactId=embedded-jetty-archetype
The command will output something like this and ask for a groupId and artifactId for your new application:
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:2.0:generate (default-cli) @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:2.0:generate (default-cli) @ standalone-pom <<<
[INFO]
[INFO] --- maven-archetype-plugin:2.0:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Interactive mode
[INFO] Archetype [net.publicstaticvoidmain:embedded-jetty-archetype:1.2] found in catalog http://maven.publicstaticvoidmain.net/archetype-catalog.xml
Downloading: http://maven.publicstaticvoidmain.net/net/publicstaticvoidmain/embedded-jetty-archetype/1.2/embedded-jetty-archetype-1.2.jar
Downloaded: http://maven.publicstaticvoidmain.net/net/publicstaticvoidmain/embedded-jetty-archetype/1.2/embedded-jetty-archetype-1.2.jar (13 KB at 58.2 KB/sec)
Downloading: http://maven.publicstaticvoidmain.net/net/publicstaticvoidmain/embedded-jetty-archetype/1.2/embedded-jetty-archetype-1.2.pom
Downloaded: http://maven.publicstaticvoidmain.net/net/publicstaticvoidmain/embedded-jetty-archetype/1.2/embedded-jetty-archetype-1.2.pom (2 KB at 6.0 KB/sec)
Define value for property 'groupId': : com.example
Define value for property 'artifactId': : helloworld
(groupId is like your Java package name and artifactId is your application name). You can safely hit return on all other prompts and go with the default choices.
Now build the project
$ cd helloworld
$ mvn install
This builds the code and generates an execution wrapper using the Maven appassembler plugin. The wrapper will execute the main method in src/main/java/Main.java
which looks like this (comments stripped):
public static void main(String[] args) throws Exception{
String webappDirLocation = "src/main/webapp/";
String webPort = System.getenv("PORT");
if(webPort == null || webPort.isEmpty()) {
webPort = "8080";
}
Server server = new Server(Integer.valueOf(webPort));
WebAppContext root = new WebAppContext();
root.setContextPath("/");
root.setDescriptor(webappDirLocation+"/WEB-INF/web.xml");
root.setResourceBase(webappDirLocation);
root.setParentLoaderPriority(true);
server.setHandler(root);
server.start();
server.join();
}
Before you run the app, you must set the REPO
environment variable to point to your local maven repository so that the execution wrapper script can find the dependencies:
$ export REPO=$HOME/.m2/repository
The application can now be executed with:
$ sh target/bin/webapp
How is this different from using mvn jetty:run
? I like jetty:run but this approach gives me two additional benefits:
- I can control my own main method. This is great for configuring the web app server just the way I want it and write any other logic needed to get my app started the way I want.
- I can run the very same command on my production environment and my webapp will start up in exactly the same way as when I test it in development.
mvn jetty:run
isn't designed for production use. At the very least it comes with the overhead of the maven wrapper, but knowing that it wasn't meant for production would make me uncomfortable using it.
Deploying my application to production now consists of a few very simple steps:
- Check out the project on the production server
- Run mvn install
- Set the production REPO location
- Run sh target/bin/webapp and pipe output to a log stream handler
Can you already feel the inner peace of using this kind of setup?