Recent articles in the Java development 2.0 series have turned the spotlight on PaaS (Platform as a Service) options for Java development. This time I invite you to check out Heroku, another popular PaaS system recently extended to support Java applications.
With roots in Ruby, Heroku's approach to Java application development and deployment is decidedly different from what you may have experienced with other Java PaaS options, notably Amazon's Elastic Beanstalk and Google App Engine (GAE). Before launching into a hands-on introduction to Heroku, I think it could be helpful to look at what it shares in common with these two platforms, as well as where it's different.
GAE and Beanstalk: Two kinds of heavyweight
As we've learned from previous articles in this series, Google App Engine and Amazon's Elastic Beanstalk are polar opposites in terms of flexibility. Whereas GAE is a pretty tight sandbox, requiring that you play by its rules or not at all, Elastic Beanstalk is fully customizable: if it runs on the JVM, Amazon's PaaS will let you use it. GAE restricts your choice of Java libraries, and it also controls most of how your application scales. In fact, deploying apps to GAE is an exercise in letting go: You won't know where your web app lives or even how many application instances are alive. On the upside, Google does all the scaling for you, and it goes without saying that GAE apps scale very well. Releasing control means that you don't have to worry about much, aside from writing rock-solid code, of course.
Amazon's Elastic Beanstalk is on the other side of the ring. In addition to giving you your choice of tools, it allows a heck of a lot control over how an application scales. The downside of this is obvious: without hands-on participation, Amazon does very little for you. Still, if you want to fine-tune your application deployment, and you want a highly scalable infrastructure, Amazon's Elastic Beanstalk is a darn good bet.
Given an ease-of-use scale encompassing both coding and deployment, with a range of 10 to 0 (10 being painful and 0 effortless), I'd put GAE at 4, and Elastic Beanstalk at 6. GAE lets me run a script and upload files seamlessly, but the limited range of compatible development tools sometimes cramps my style. Conversely, Elastic Beanstalk lets me use any library I like, but the road to deployment is longer and sometimes more tedious; I take on a measure of complexity in exchange for more control.
Like GAE and Elastic Beanstalk, Heroku is built to scale horizontally. You deploy your code into what Heroku calls dynos, which are basically web containers. If you want to scale your system, just add more dynos, thus enabling Heroku to handle more simultaneous web requests. It's a simple concept that provides more control than GAE without the configuration requirements of Elastic Beanstalk.
On Heroku, Git — not Ant or Maven — is your deployment pipeline. When you're ready to deploy your application, you do so via a Git push, something I'll discuss in-depth later in the article.
As compared to GAE and Elastic Beanstalk on the ease-of-use scale, Heroku is a 2, which means it's pretty close to effortless. Heroku gives me great flexibility in terms of tools, so I can choose the most productive tools for any given job. In terms of configuration, Heroku allows more control than GAE, but less than Elastic Beanstalk — which sometimes is just right. It's also easy to port an application built on Heroku to Elastic Beanstalk: If I ever need more fine-grained control over an application's scalability, I know that I can just move it on over!
To get started with Heroku, you'll need to install and set up the following:
- Ruby and RubyGems (Heroku's command-line tool is written in Ruby and you install it via RubyGems)
- A Heroku account, which is free to start
- Git
- Maven
Note that Maven isn't required for Heroku, I'm just using it as my build tool. Heroku's Java documentation is also based on Maven.
Once you have everything installed, pick the directory you'll be working in and run the following Maven command (code broken to accommodate page width):
mvn archetype:generate -DarchetypeGroupId=org.mortbay.jetty.archetype -DarchetypeArtifactId=jetty-archetype-assembler -DarchetypeVersion=7.5.0.RC0 |
Maven will prompt you to provide a groupId and an artifactId. I typically use my package name for groupId and my project name for artifactId. Maven will then generate a project structure capable of building and running a web application on top of Jetty. Thus, in one step, you have the skeleton of a web application ready to be deployed into Heroku’s cloud infrastructure. While Jetty is the Maven archetype used, Heroku doesn't just support Jetty on the web server side. In fact, Heroku doesn't even know about Jetty — nor does it care.
The default app generated by the previous Maven command is your run-of-the-mill, pedestrian "hello world" application. In fact, if you go into the app's src directory, followed by main—>webapp, you’ll see an index.html file. Run the application and that file will (as you've probably guessed) print out the text "hello world."
Running the application is also easy. If you type the command mvn install in your newly generated project directory (which bears the name you gave for artifactId), a shell script will be generated for your desired operating system (mine is OSX). Just type $>sh target/bin/webapp, then go to http://localhost:8080 in your favorite browser, and prepare to be greeted.
With Heroku, you can deploy any Java library you like. For the sake of example, and familiarity — at least for those of you who have been reading for a while — I’m going to build another incarnation of my location-gathering mobile web service, Magnus, which debuted in my introduction to Amazon's Elastic Beanstalk (see Resources). For my RESTful library, I’m going to use Apache Wink, which is an implementation of the JAX-RS specification. My web service implementation will provide a PUT endpoint that accepts JSON and inserts relevant data obtained from the document into a MongoDB instance. That, in turn, will be hosted at MongoHQ, by way of Morphia (see Resources).
The first thing I have to do is update Magnus's Maven pom.xml file with the new Wink and Morphia dependencies, as shown in Listing 1:
Listing 1. Adding Wink and Morphia to a Maven POM
<dependency> <groupId>org.apache.wink</groupId> <artifactId>wink-server</artifactId> <version>1.1.3-incubating</version> </dependency> <dependency> <groupId>org.apache.wink</groupId> <artifactId>wink-json-provider</artifactId> <version>1.1.3-incubating</version> </dependency> <dependency> <groupId>com.google.code.morphia</groupId> <artifactId>morphia</artifactId> <version>0.99</version> </dependency> |
Note that I also update my POM file to look inside Morphia's Maven repository and obtain version 0.99:
Listing 2. Adding a new repo to a Maven POM
<repositories> <repository> <id>morphia repository</id> <url>http://morphia.googlecode.com/svn/mavenrepo/</url> </repository> </repositories> |
Next, I create a location resource — a Wink endpoint that will represent user locations:
Listing 3. Creating a Wink LocationResource
@Path("/location")
public class LocationResource {
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Path("{id}")
public String updateAccountLocation(@PathParam("id") int accountId, JSONObject
requestJSON) {
try{
double latitude = Double.parseDouble(requestJSON.get("latitude").toString());
double longitude = Double.parseDouble(requestJSON.get("longitude").toString());
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy HH:mm");
Date dt = formatter.parse(requestJSON.get("timestamp").toString());
new Location(accountId, dt, latitude, longitude).save();
return new JSONObject().put("status", "success").toString();
}catch(Exception e){
return "failure with " + requestJSON;
}
}
}
|
Wink has me describe my RESTful service via three annotations: One for my HTTP method
(PUT), another for my expected request content type (JSON), and the last to indicate that the endpoint accepts a parameter (in this case location/:accountId).
This Location class is the same Morphia-backed object introduced in my introduction to Elastic Beanstalk. It simply creates a document in MongoHQ that represents a location for a given account. The location (theoretically received from a mobile device) is represented by the RESTful endpoint's parameter.
Next, I want to hook up Wink with Jetty, so I need to do two things: create an Application class and configure things in my web.xml file.
The purpose of the Wink Application class (shown in Listing
4) is to load corresponding resource classes:
Listing 4. A Wink Application class
public class MarmarisApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<Class<?>>();
classes.add(LocationResource.class);
return classes;
}
}
|
In Listing 5, I then update my app's web.xml file, adding attributes specific to Wink, such as a
pointer to my Application class and my desired URL pattern, which in this case is /service/resource:
Listing 5. Hooking up Wink via web.xml
<servlet> <servlet-name>MarmarisApp</servlet-name> <servlet-class>org.apache.wink.server.internal.servlet.RestServlet</servlet-class> <init-param> <param-name>javax.ws.rs.Application</param-name> <param-value>com.b50.marmaris.MarmarisApplication</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>MarmarisApp</servlet-name> <url-pattern>/service/*</url-pattern> </servlet-mapping> |
As a test, try re-running the mvn install command and
starting the app (in this case Magnus) locally. You should be able to submit a JSON
document (shown in Listing 6) to the endpoint http://localhost:8080/service/location/{account_id}, where account_id is a number.
Listing 6. A JSON document representing a location
{
"name":"location payload",
"latitude":"46.49",
"longitude":"71.11",
"timestamp":"09-02-2011 14:43"
}
|
Writing this application wasn't hard, and now we're ready for the even easier part: deploying it in Heroku's cloud!
Heroku's deployment pipeline is Git, which can take some adjustment if you're
unfamiliar with distributed version control. Deploying to Heroku (via Git) is similar to issuing a Subversion commit into a different branch off the mainline. But in this case, Heroku is not the main code repository; it's just an alternate remote repository. To deploy an application, you push its source code via Git.
Note that rather than deploying war files, Heroku has you deploy your project as is. As you'll see when we start building our application's Procfile, Heroku is just a JVM looking for some code to execute. (If you want to see now what I'm talking about, check out the generated Main.java file in your project's package structure, then correlate its entry in your POM.)
While not immediately intuitive, Heroku's deployment model makes sense once you start using it. What's more, Heroku's tight integration with Git makes pushing various branches to different environments quick and painless.
Deployment set up happens in two steps: First, you need to create and commit your code to a local Git repository. You can do this in your project's root directory, by typing the commands in Listing 7 in a terminal:
Listing 7. Git initialization and commit
$> git init $> git add . $> git commit -m "initial commit before Heroko deployment" |
Now your local Git repository has a snapshot of your code (that is, the code has been versioned).
Next, you'll create an application in Heroku. I've done it in Listing 8 via the heroku command-line client (here's where it's essential that you have Ruby and RubyGems installed):
Listing 8. Creating a Heroku application
$> heroku create marmaris --stack cedar |
The heroku create command in Listing 8 creates an application named "marmaris" on the cedar stack. Note that you'll have to choose a different application name, as that one is now taken. Alternately, you can leave the name to Heroku, and it will generate a unique one for you.
Heroku has a number of stacks. Cedar supports Java and Node.js, while others (like Bamboo) support newer versions of Ruby. When you execute the heroku create command, it will update your Git configuration and add a remote repository dubbed heroku.
Before you can deploy your code base to Heroku, you need to tell it how to run your application. This is easily done with a Procfile, which is simply a text file stating a command. My Procfile, shown below, points to the webapp shell script found in my target directory.
Listing 9. Creating a Procfile
$> echo 'web: sh target/bin/webapp' > Procfile |
After creating a new file, it's important that you notify Git; otherwise Heroku won't know about it when you deploy the app via a Git push.
Listing 10. Notifying Git
$> git add Profile $> git commit -m "updated to include my Profile" |
Finally, to deploy the application, you'll simply issue a Git push to the heroku remote repository,
shown in Listing 11:
Listing 11. Deploying to Heroku
$> git push heroku |
You should see a series of messages returned by heroku, but look for the one that says something like:
http://your_app_name.herokuapp.com deployed to Heroku |
Enter that URL in your favorite browser and (assuming you've left the default index.html file in your project directory) you will see a "hello, world!" printout.
While "hello, world!" is always quite interesting, the purpose of this application was to accept location information via HTTP PUTs. Consequently, using WizTools.org's RESTClient, I can issue an HTTP PUT against my RESTful endpoint, provide a JSON document, and bingo! I send a nice JSON response with the wonderful word success.
Scaling and maintaining Heroku
Heroku by default runs your app on a single dyno. This dyno, which is free, essentially turns itself off in times of no activity and spins itself back up when a request comes along. If you need to scale your app, you add more dynos, which you can do via the heroku scale command:
Listing 12. Scaling the application
$> heroku scale web=2 |
If you ever need to, you can also scale back an application by reducing the number of requested dynos. For instance, in this case I'd scale back to web=1. You can do both of these operations via Heroku's web interface, as well.
You can also tail logs on Heroku via the heroku logs command
in Listing 13:
Listing 13. Watching logs on Heroku in real time
$> heroku logs -t |
The heroku command-line client supports numerous features to scale, monitor, and manage your application; see Resources for documentation. Heroku also supports many third-party add-ons, in addition to a default datastore. I recommend checking out Heroku's support for MongoHQ and PostgreSQL.
Heroku's tight integration with Git presents a new paradigm for deploying and scaling Java apps via the cloud, but it also makes the process exceptionally easy and powerful. All in all, having the freedom to use unlimited Java libraries and then deploy the resulting applications near-effortlessly is something that definitely works for me.
Learn
- Java development 2.0: This dW series explores technologies that are redefining the Java development landscape. Topics have included
Amazon's Elastic Beanstalk (February 2011); Google App Engine (August 2009); RESTClient (November 2009); Gretty (August 2011); and MongoDB (September 2010).
- "Git changes the game of distributed Web development" (William von Hagen, developerWorks, August 2009): Distributed version control isn't new, but Git's unique support for collaboration and interaction among developers is a true game changer.
- "Introduction to Maven 2" (Sing Li, developerWorks, December 2006): If you need an introduction to Maven, this tutorial will guide you through it from start to finish.
- "Using Apache Wink, Eclipse, and Maven to develop RESTful Web services" (Gabriel Mateescu, developerWorks, February 2011): Learn more about Wink and Maven working together in RESTful application development.
- Knowledge path: Introduction to Platform as a Service (March 2011): Your guide to learning about PaaS.
- "Cloud and
industry, Part 1: PaaS best practices and patterns" (Yu Chen Zhou, et al,
developerWorks, December 2010): Learn concepts for
cloud-enabled industry solution practices, patterns, and models.
- Cloud computing service models, Part 2: Platform as a Service (Dan Orlando,
developerWorks, January 2011):
- "Java PaaS
shootout" (Michael Yuan, developerWorks, April 2011): A technical comparison of
Google App Engine, Amazon Elastic Beanstalk, and CloudBees RUN@Cloud
-
Browse the
Java technology bookstore for books on these and other technical topics.
-
developerWorks Java technology zone: Find hundreds of articles about every aspect of Java programming.
Get products and technologies
- Get started with Heroku.
- Download Ruby and RubyGems.
- Play, Gretty, and Wink are three popular options for building RESTful applications.
Discuss
- Get involved in the developerWorks community. Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.

Andrew Glover is a developer, author, speaker, and entrepreneur with a passion for behavior-driven development, Continuous Integration, and Agile software development. He is the founder of the easyb Behavior-Driven Development (BDD) framework and is the co-author of three books: Continuous Integration, Groovy in Action, and Java Testing Patterns. You can keep up with him at his blog and by following him on Twitter.




