WAR-less Java Web Apps

Have you ever thought about why in Java we package up web apps into WAR files (or WAR directory structures)? It certainly is a convenient way to move an application and its dependencies from one place to another. But wouldn’t it be nice if everything could just stay in its original location and there wouldn’t be any moving of files around? Wouldn’t it also be nice if you specified your required version of Jetty or Tomcat just like you do with every other dependency? The WAR-less approach is one that is catching on as emerging Java web frameworks like Play! ditch the WAR files. With standard Java web apps we can also ditch the WAR files by simply launching an embedded Jetty or Tomcat server. Let’s give this a try and see how it goes.

For this experiment I’m going to use Maven and Jetty. This will still use the same standard source structure for a WAR file (src/main/java, src/main/webapp, etc). The major difference is that I will actually startup Jetty using a good-old static void main. This is similar to using the jetty:run goal but will allow us to have the same exact setup in development and in production. The static stuff will be in src/main/webapp, the compiled classes will be in target/classes, and the dependencies will be right were Maven downloaded them to. First, here is a little Java class (src/main/java/foo/Main.java) that sets up a Jetty server and starts it:

package foo;
 
import java.io.File;
import java.net.URL;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
 
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.*;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.webapp.WebAppContext;
 
public class Main
{
 
  public static void main(String[] args) throws Exception
  {
    String webappDirLocation = "src/main/webapp/";
 
    Server server = new Server(8080);
    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();
  }
}

As you can see, Main just references the webapp directory so I don’t have to copy the stuff from there to another place. Next I have a little test servlet (src/main/java/foo/HelloServlet.java):

package foo;
 
import java.io.IOException;
import java.io.PrintWriter;
 
import javax.servlet.ServletException;
import javax.servlet.http.*;
 
public class HelloServlet extends HttpServlet
{   
 
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
  {
    PrintWriter out = resp.getWriter();
    out.println("hello, world");
    out.close();
  }
}

And now the web.xml file (src/main/webapp/WEB-INF/web.xml):

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  version="2.5">
  <servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>foo.HelloServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

And finally a pom.xml file that specifies Jetty as a dependency and provides an easy way to run the Main class:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.jamesward</groupId>
  <version>1.0-SNAPSHOT</version>
  <name>warless_java_web_app</name>
  <artifactId>warless_java_web_app</artifactId>
  <packaging>jar</packaging>
 
  <properties>
    <jettyVersion>7.3.1.v20110307</jettyVersion>
  </properties>
 
  <dependencies>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-server</artifactId>
      <version>${jettyVersion}</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-webapp</artifactId>
      <version>${jettyVersion}</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-servlet</artifactId>
      <version>${jettyVersion}</version>
    </dependency>
  </dependencies>
 
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.2</version>
        <configuration>
          <mainClass>foo.Main</mainClass>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

And now simply run:

mvn compile exec:java

Maven compiles my Java classes into target/classes and then the exec:java goal runs the Main which finds the other WAR assets in the src/main/webapp directory. If you have been following along, make a request to http://localhost:8080/ to verify that it works (which it should).

There are two alternatives to running Jetty from Maven. You can use the Maven appassembler plugin to create start scripts containing the correct CLASSPATH references and then launch Main class using the generated scripts. Or you can use the Maven assembly or shade plugin to create a JAR containing the application and all of its dependencies.

Here is an example section of a pom.xml file for using the appassembler plugin:

  <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>appassembler-maven-plugin</artifactId>
    <version>1.1.1</version>
    <configuration>
      <assembleDirectory>target</assembleDirectory> 
      <generateRepository>false</generateRepository>
      <programs>
        <program>
          <mainClass>foo.Main</mainClass>
          <name>main</name>
        </program>
      </programs>
    </configuration>
    <executions>
      <execution>
        <phase>package</phase>
        <goals>
          <goal>assemble</goal>
        </goals>
      </execution>          
    </executions>
  </plugin>

To generate the start scripts simply run:

mvn install

Then to run the script set the REPO environment variable to your Maven repository:

export REPO=~/.m2/repository

And then simply run the script:

sh target/bin/main

All of the code for this example is on github.com:
https://github.com/jamesward/warless_java_web_apps

To make all of this even easier, Jetty has a Maven archetype for generating everything for you. To create a new project containing this setup run:

mvn archetype:generate -DarchetypeGroupId=org.mortbay.jetty.archetype -DarchetypeArtifactId=jetty-archetype-assembler -DarchetypeVersion=8.1.9.v20130131

And now you are ready to build a WAR-less Java web app!

This setup is really the bare minimum required to handle web resource and servlet requests you will need to do a little more work if you want to add JSP support. Find out more about this in the Jetty documentation.

So… What do you think about Java Web apps without WAR files & WAR packaging? I’d love to hear your thoughts!

This entry was posted in Java. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.
  • Pingback: http://links.soudev.com.br

  • Seth

    I’ve often wondered what inspired the Java packaging schemes. Look at the web content model… Create a zip… overwrite content, automatic pickup of the change. If you are asking the question “war what it it really good for?” go one step further and ask the question… wtf I have to restart every time I make a change? I know… I know… VMs these days don’t need to… but does anyone actually trust them to do hot deploys?

    Even better create a NAS, deploy this imaginary new java as “content” model to shared location and then point your Jetty instances to that…. and then load balance an Apache instance across as many “servlet” containers as you heart desired. You could easily create a model where one Apache instance was hitting 20 or 30 jetty instances with hardly any work. Then work up to the multi-site model and there you have some scale…

    Try that model in those were proponents of J2EE… Sun, IBM, Oracle… feels like the packaging schemes were created to sell software and weren’t about getting the job done.

  • http://silvanolte.com/blog Daniel

    Nice tips. This reminds me of how Grails works using grails run-app, or Spring Roo scaffolded apps using mvn tomcat:run. These are also ways to develop WAR-less.

    But at the end of day, if you need to deploy, you need to generate the WAR…

    … or maybe not. How cool would it be if we could instead upload a configuration file to an app server that will tell it where to get the Production code on the cloud. Now that would be really cool.

  • Bill Robertson

    Neat idea, you see this in other languages/libraries. Http server is so ubiquitous, that its just a bit more code that you mix in to your app. I can see it for dev testing. Its very useful for that.

    Trying to come up with possible drawbacks…

    You’re relying on Maven to start the app? I’ll pass on that. If you’re audited (e.g. SOX compliance, lawsuit, “partners” (i.e. people looking to blame you for their mistakes), ugh whatever), could you prove what version of the code you were/are running at a given time? (not that most organizations could do that today with their current process).

    Also, what do you do if you if you’ve got multiple web apps to run on the same machine?

  • Pingback: Embedding Jetty with Netbeans 7 (with Maven) | Giant Flying Saucer

  • http://scratchpad101.com Kapil Viren Ahuja

    Hi James – We have been using this approach for some time and are using it for applications that need to be run for small enterprises. We have found it very useful and convenient.

    However, thanks for the integration with Maven, we are going to shoot for maven integration next month and your post will certainly help us.

  • Pingback: Containerless Spring MVC

  • Pingback: Maven + Jetty = Quick WebService « Dev-Smart

  • Melissa M

    How about if you want to be able to run the war using java -jar myWebApp.war i.e. not using Maven? It seems like it’s a pretty complex task to get jetty to recognize all the libraries needed!

  • Jason Young

    The mvn archetype:generate command above is broken:

    [ERROR] Failed to execute goal org.apache.maven.plugins:maven-archetype-plugin:2.2:generate (default-cli) on project standalone-pom: The desired archetype does not exist (org.mortbay.jetty.archetype:jetty-archetype-assembler:7.5.0-SNAPSHOT) -> [Help 1]

    • http://www.jamesward.com James Ward

      Try:
      mvn archetype:generate -DarchetypeGroupId=org.mortbay.jetty.archetype -DarchetypeArtifactId=jetty-archetype-assembler -DarchetypeVersion=8.1.9.v20130131

  • Pingback: WAR files vs. Java apps with embedded servers » StevePerkins.net

  • Pingback: Should An Embedded Jetty Server Be A Spring Bean | Topics in Software Engineering



  • View James Ward's profile on LinkedIn