Most server-side Java applications (e.g. web or service-oriented) are intended to run within a container. The traditional way to package these apps for distribution is to bundle them as a WAR file. This is nothing more than a ZIP archive with a standard directory layout, containing all of libraries and application-level dependencies needed at runtime. This format is mostly interoperable, and can be deployed to whichever server container you like, Tomcat or Jetty, JBoss or GlassFish, etc.
However, there is another school of thought which completely turns this model on its head. In this approach, Java applications are packaged for command-line execution like any normal app. Rather than being deployed to a container, an embedded container is deployed within the application itself!
This is par for the course with many languages. The Django framework for Python includes a bundled server for development and testing, while Ruby on Rails comes with an embedded server that is used in production as well. The concept has even been around for awhile in Java, with Jetty specializing in the embedded niche. However, this is far from the norm, and the de facto standard is still a WAR file that can be deployed to Tomcat.
The buzz is growing, though. At last year’s DevNexus conference, I went to a session by James Ward, who at that time was a “developer evangelist” at Heroku. Bundling your own container is the recommended approach for deploying apps to Heroku’s cloud-based platform, and James is a big proponent.
His session was specifically about the Play framework for Java and Scala, which embeds Netty in a manner similar to the Rails server. Unlike Grails, which uses a Django-style server for development and then ships as a WAR file, Play is meant to use its own server all the way to production. James advocated this approach in all Java apps.
An embedded adventure
I had at least a sip of the Kool-Aid. When I started writing my book, Hibernate Search by Example, I wanted to keep its focus on Hibernate Search rather than any other frameworks or server issues. So I eschewed Spring, and wrote the book’s example application using a vanilla Servlet 3.0 approach.
I normally use Eclipse in my own development environment, and point it at a local Tomcat instance for testing web apps. However, I wanted to support readers who were more comfortable using IntelliJ, Netbeans, or no IDE at all. So I decided to have my build script embed a test server, so readers could run the examples without installing or configuring anything.
Using an embedded server with Maven
My first goal was simply to launch a server from within my Maven build scripts, so the readers wouldn’t have to install a server or integrate it into their IDE. I had seen this done before, and it was a simple matter of adding the Jetty Maven Plugin to a project’s POM. Readers should be able to build the example application and launch it in one step, with the command:
mvn clean jetty:run
Eh, not so fast. You’re supposed to be able to make changes to your static content, and see the changes immediately take effect while the server is running. However, I ran into errors about my files being locked. After wasting some time on research, I discovered that Jetty’s default settings do not play nice with Windows file locking. This can be fixed by toggling one property in one config file.
However, you have to crack open a Jetty JAR file to get a correct copy of this config file. First, you have to dig around your local Maven repo and figure out which JAR file to crack open (it turns out to be jetty-webapp rather than jetty-server). Once you get a copy of the webdefault.xml file and toggle the useFileMappedBuffer setting, you have to save your copy somewhere in your project, and update your Maven POM to look there rather than inside the Jetty JAR:
<plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>8.1.7.v20120910</version> <configuration> <webAppConfig> <defaultsDescriptor>${basedir}/src/main/webapp/WEB-INF/webdefault.xml</defaultsDescriptor> </webAppConfig> </configuration> </plugin>
Okay… a little more hassle than I was expecting, but I can deal with this.
Using an embedded server with other build systems
I know that many Java developers hate Maven. So I wanted to provide a version of my book’s example application built using Ant, to illustrate how the default concepts can be adapted. So, which line do I add to build.xml to make Ant use Jetty?
Eh, not so fast. There is Ant integration for Jetty, but it is even more cumbersome than Maven. Even if you are using a dependency-management system such as Ivy, your Ant script can’t download and manage the embedded server for you. Instead, you have to download a full standalone Jetty server, and manually copy bits and pieces of it into your project. Who doesn’t want 6 megs of executable binaries committed into source control?
After you copy over the Jetty server JAR’s, you need to manually add another JAR file for the Ant integration. To my surprise, I discovered that the most recent supported version was Jetty 7, implementing the Servlet 2.5 spec that is almost eight years old.
I see that they finally added Jetty 8 last month, but that didn’t help me when I was writing the book last autumn. I had to re-write this version of my example app for Servlet 2.5 instead of 3.0, and was starting to wonder if this was really worth it.
Using an embedded server from code
This last chapter of my book talks about Hibernate Search applications running in a clustered server environment. The Maven plugin is purely single-instance, so I decided to write a small bootstrap class that would pro grammatically launch two Jetty instances on different ports. By structuring this class as a JUnit test, I could still have Maven launch it automatically like this:
mvn clean compile war:exploded test
Eh, not so fast. My application’s servlets, listeners, and RESTful services were not being registered at startup. After much more wasted research time, I discovered that there are different “flavors” of Jetty available, with Servlet 3.0 features (such as annotations) enabled or disabled by default.
To be honest, I still don’t completely understand how to tell the difference between “hightide” and “non-hightide”. All I can tell you is that I had to add this hunk of code to my bootstrap class in order to make annotations work:
... masterContext.setConfigurations( new Configuration[] { new WebInfConfiguration(), new WebXmlConfiguration(), new MetaInfConfiguration(), new FragmentConfiguration(), new EnvConfiguration(), new PlusConfiguration(), new AnnotationConfiguration(), new JettyWebXmlConfiguration(), new TagLibConfiguration() } ); ...
So much simpler and more intuitive than dropping a WAR file in Tomcat’s /webapps folder, right? 🙂
Using an embedded server from the console and the cloud
With the book complete, I wanted a demo version of the example code pushed to GitHub and deployed to Heroku. Theoretically, Heroku can run any application that you can run locally from the command-line. If Heroku finds a Maven POM, it will run mvn clean package, and then execute whatever startup command you have placed in a script named Procfile.
My programmatic Jetty launcher worked fine within the context of a Maven run. However, Maven was managing my classpath dependencies at test time, and now I need Jetty available without that help. Heroku’s recommended approach, used in their demo Java applications, is to bundle your app with a one-file version of Tomcat. Awesome, I’m more familiar with Tomcat anyway!
Eh, not so fast. If your application expects database connections (or anything else) to be registered as JNDI resources, then you are on your own. Heroku’s bundled Tomcat runner doesn’t support JNDI setup. Hmm… maybe this is why Heroku’s vanilla servlet demo doesn’t really do anything, and why the only demo app that does do something is Spring-based instead. Now that I think about it, James Ward left Heroku to work for TypeSafe last year, and Heroku hasn’t made a single update to their Java site since he left. Gulp.
Don’t worry, because there is a similar one-file Jetty Runner, and it does let you pass JNDI settings as command-line parameters. Besides, we’ve invested a lot of time solving all the problems with embedded Jetty already!
Eh, still too fast. If you use JSTL taglibs in your JSP views (i.e. you live in the 21st century), then Jetty Runner is a mess that puts you in classpath hell. When running it from the command-line, you need to pass parameters to Java for:
- The Jetty Runner JAR file
- Your web application’s WAR file (*)
- The exploded version of your WAR file, generated during the Maven build
(*) You read that correctly. After all of this embedded nightmare, Heroku is actually still using a WAR file!!!
My Heroku Profile ended up looking like this:
web: java $JAVA_OPTS -jar target/dependency/jetty-runner-8.1.7.v20120910.jar --lib target/hibernate-search-demo-0.0.1-SNAPSHOT/WEB-INF/lib --port $PORT --jdbc org.apache.commons.dbcp.BasicDataSource "url=jdbc:h2:mem:vaporware;DB_CLOSE_DELAY=-1" "jdbc/vaporwareDB" target/*.war
There is more than one classloader at work here, and this allows the Jetty Runner to load the JSTL/taglib stuff from its classpath rather than the web app’s classpath.
Conclusion
There is nothing inherently wrong with the embedded server concept, when it is baked-in to a framework from the outset. Writing Play applications is a pleasure, and they are almost trivial to deploy on Heroku. At my day job, I use a Spring-based commerce package called hybris, whose extensive build system bundles a Tomcat server into your app. As long as you don’t need to customize the build scripts too much, this works fine.
On the other hand, the concept is just too fragile and brittle for wide general-purpose use. Duct taping an embedded server onto a normal Java application is pure pain. You might be able to cling to the safety of someone else’s working example, but the moment your app does anything different, you are on your own to fix the breakage. Take my embedded adventure above, and contrast it with the “hassle” of using Tomcat:
- Download Tomcat and unzip it somewhere
- Drop your WAR file in Tomcat’s /webapps subdirectory
- Start Tomcat
The only real advantage I gained was the ability to run a demo on Heroku. However, Java support from cloud providers is improving every day. Jelastic lets you deploy normal WAR files to Tomcat 7 or GlassFish 3 right now. AppFog supports deployment to Tomcat 6, with support for Tomcat 7 coming soon. I suspect that in the not-so-distant future, the idea of modifying your apps for cloud deployment will be seen as an anachronism.
So in a nutshell, it depends on the framework you’re using. If embedded servers are baked-in, then they can be very cool. If they are duct taped-on, then they can be horrible. If I were writing Hibernate Search by Example today, the example application build scripts would produce two things: a WAR file, and a Tomcat download link.