Deploy Containerless Tapestry Apps on Heroku

Recently I spent some time with Howard Lewis Ship, creator of the Apache Tapestry web framework. Howard is a technical rock star so it was really fun to sit down with him and hack on some code. Our goal was to make it easy for people to run their Tapestry apps on the cloud with Heroku. You can run anything on Heroku so there are a variety of ways to run Tapestry apps on Heroku. We wanted to put together something that helps Tapestry users run their apps in the most optimal way. What we came up with is available in Howard’s tapx-heroku package on GitHub. Lets walk through what it does.

The JettyMain class provides a simple way to start a Java web app using an embedded Jetty server. This “Containerless” method has these advantages over the traditional container deployment model:

  • The HTTP handling library (Jetty in this case) is a dependency of my application. This alleviates pain caused by differences in developer and production deployment environments.
  • My app is just a plain Java library so it can be packaged as a regular JAR file and used as a dependency itself.
  • The application starts up super fast.
  • I have a way to setup the application (like the session storage provider) in code instead of having to do it in XML.
  • Development cycles are faster as a result of lightweight packaging and deployment. With IntelliJ or JRebel class hot-swapping can make those cycles even shorter. (This is also an out-of-the-box feature with Tapestry.)

To use JettyMain with your Tapestry app, we just need to update the Maven build (pom.xml) file. You can do this with an existing app or start from scratch using the Tapestry Maven Archetype. Here is a simple pom.xml file:

<?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>org.example</groupId>
    <version>1.0-SNAPSHOT</version>
    <name>herokuapp</name>
    <artifactId>herokuapp</artifactId>
    <packaging>jar</packaging>
 
    <repositories>
        <repository>
            <id>howardlewisship</id>
            <url>http://howardlewisship.com/snapshot-repository</url>
        </repository>
    </repositories>
 
    <dependencies>
        <dependency>
            <groupId>org.apache.tapestry</groupId>
            <artifactId>tapestry-core</artifactId>
            <version>5.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.howardlewisship</groupId>
            <artifactId>tapx-heroku</artifactId>
            <version>1.2-SNAPSHOT</version>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.4</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals><goal>copy-dependencies</goal></goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
 
</project>

This specifies Tapestry and the tapx-heroku libraries as dependencies. The tapx-heroku library will automatically pull in the Jetty dependencies. The maven-dependency-plugin will copy the runtime dependencies from the local Maven cache to the target/dependency directory so that we can easily setup the application’s runtime classpath.

To build the app locally just run:

mvn package

To launch the application run:

java -cp target/dependency/*:target/classes com.howardlewisship.tapx.heroku.JettyMain

That’s it! Tapestry, tapx-heroku (JettyMain), Jetty, your application classes, and the rest of the required dependencies are all loaded into the JVM and the web server is started. Super simple stuff!

The JettyMain app has a few optional command line arguments for overriding the defaults. You can see those by running:

java -cp target/dependency/*:target/classes com.howardlewisship.tapx.heroku.JettyMain -help

If you want to try out a simple app that already has this setup then I have one on GitHub that you can copy locally by doing a git clone:

git clone https://github.com/jamesward/hellotapestry.git

Now lets run this on the cloud with Heroku. To follow along you need to install the Heroku toolbelt and signup for a Heroku.com account. Don’t worry, you won’t need to enter a credit card to give this a try because Heroku gives you 750 free dyno hours per application, per month. (Wondering what a “dyno” is? Check out: How Heroku Works)

We need to tell Heroku what process on the system to run in order to start up the web application. To do this we need a file named Procfile (with a capital ‘P’) in the project’s root directory. The contents of that file should be:

web:   java -cp target/dependency/*:target/classes com.howardlewisship.tapx.heroku.JettyMain

In this case we will upload the source up to Heroku where it will run the Maven build. This is the standard Heroku deployment mechanism because it makes the deployment process super fast and makes sure that there are no discrepancies between the different environments. The dependencies are downloaded on the cloud (i.e. very quickly) instead of doing the typical lengthy package and upload process. But you can also upload binary assets if you prefer. Heroku uses git for file upload so if you don’t already have the project files in a git repo then you need to create the repo, add the files, and commit them:

git init
git add src pom.xml Procfile
git commit -m init

Now we can provision an application on Heroku by logging in from the Heroku CLI and creating an application on the “cedar” stack:

heroku login
heroku create -s cedar

The application can now be uploaded using git:

git push heroku master

When the upload has finished Heroku will run the Maven build, deploy the compiled application onto a dyno, and start the web process defined in the Procfile. Test out the application in your browser by running:

heroku open

Great! You now have a Tapestry app running on the cloud! But there is one other cool thing the JettyMain does for us. Java web applications often use in-memory session state which does not work well with the server affinity model that is necessary for scalable cloud deployments. Out-of-the-box Tapestry uses the session state very minimally but it’s possible that your application uses it more heavily. Heroku dynos are intended to be stateless so they can be managed, scaled, and configured without disrupting active users. This means that the session state needs to be in an external store. Luckily Jetty makes it easy to transparently move session state to an external MongoDB system (see my Using MongoDB for a Java Web App’s HttpSession post for more details).

The JettyMain app has this functionality built in as long as you either set the mongo-url application parameter or have set either a MONGOHQ_URL or MONGOLAB_URL environment variable. On Heroku it’s very easy to create a MongoDB system for your application to use. The Heroku add-ons provide numerous Cloud Services to Heroku apps, including two MongoDB providers: MongoLab and MongoHQ. Both have free tiers, so pick one and then add it by running either of these commands:

heroku addons:add mongolab
heroku addons:add mongohq

This will automatically provision the MongoDB system, set the connection information in the expected environment variable, and then restart your dyno(s) so that they pickup the new configuration. The JettyMain will now switch Jetty’s session storage provider to the MongoDB system instead of using in-memory storage. Cool stuff!

Let me know if you have any questions and thanks Howard for putting this together!

This entry was posted in Heroku, Java, Tapestry. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.
  • Howard Lewis Ship

    Funny; I couldn’t get it to work; turns out I put a typo into the package name for JettyMain: it was built as “com.howardlewiship”, not “com.howardlewisship”; i.e., missing an ‘s’.  I’ll correct it shortly, but keep it temporarily compatible; once I have a new version up, I’ll let you know, to edit the class name.

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

      Haha.  Maybe I typed that one when we were pair programming.  :)

  • Howard Lewis Ship

    I’ve uploaded a new version of tapx-heroku; both package names should work for the meantime. Please update the article when you get a chance.

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

      Ok, that has been updated here and in my sample project.

  • Hugo Palma

    Great tutorial.
    Just a note, if you include tapestry-test as a dependency of your project this doesn’t work. Maven dependency trouble. Still trying to figure out a solution.

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

      Hmm…  I’ll look into that too.  Let me know if you figure anything out.

  • http://twitter.com/6Builder George Ludwig

    The tutorial is totally appreciated! I have once question for James…is there a way for Heroku to auto scale the app the way AWS Elastic Beanstalk does?

  • MGTobias

    Hi,
    I have a problem with the “java -cp” command.

    I downloaded your example from github and ran “mvn package”successfully. If I execute the java -cp […] command, I recieve the error message “no matches found: target/dependency/*:target/classes”.

    Do you have any idea why this happens? Thanks a lot!

    – Tobi

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

      Are you on Windows? If so I think you need to wrap the classpath in double quotes or something funky like that.

      • MGTobias

        Good morning,

        no, I’m on Mac OSX. Hmm. I just “copy-pasted” the line to avoid any mistakes… To type it by hand – no differences.

        – Tobi

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

          Try the string wrapping, like: java -cp “target/dependency/*:target/classes”

          • MGTobias

            That’s the soloution. You rock! :)

  • MGTobias

    Folks, please note. The “java -cp target/dependency/*:target/classes com.howardlewisship.tapx.heroku.JettyMain” command, does’nt work with “zsh” you have to use your bash aka “sh”.

    This is a awesome tutorial! Thanks for it. :)

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

      Ah. Interesting. Thanks for discovering the issue and posting it here!

      • MGTobias

        No problem, sorry for my previous postings. But I like my “lm-my-zsh” so much, that i forgot to change it to the bash. Rock on!

  • MGTobias

    But I have one last question.
    The Tapestry Quickstart application has a Jetty-Plugin included. which allows you, to start the application with “mvn jetty:run”. Heroku is based on Maven… So, why we cannot use this simple Maven command instead of the big java -cp command? Thank you for giving an answer.

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

      The deployed app on Heroku doesn’t have Maven, so you can’t run it that way there. Heroku’s Java support initially had that, but dropped Maven to reduce the “slug” size.

      • MGTobias

        Ah, okay, thanks! :)

  • http://twitter.com/k1ckedinthehead Dennis Holden

    unfortunately it breaks the hot deployment feature :/



  • View James Ward's profile on LinkedIn