Introduction to Heroku for Java Developers
Last updated October 16, 2024
If you are an enterprise Java developer or server admin who develops, deploys and operates applications on traditional Java EE application servers, this guide will help you understand how to develop and deploy Java applications on Heroku.
Built-for-deployment applications
Heroku is a platform explicitly designed for development and deployment of applications. This is different from the “classic” software delivery process which flows something like this
- Development
- Packaging
- Distribution
- Install
- Deployment
Heroku is used by organizations who develop, deploy and operate an application within a single team or a few teams. There is no packaging, distribution or install element because the code never leaves the team/organization. This is a key reason why Heroku is more agile and once you have this in mind, it’s easier to decide how to best use Heroku as your deployment platform.
Here are some areas where a built-for-deployment application differs from a built-for-distribution application:
Version control is the central distribution mechanism
There is no reason to build a package of your code that can be consumed by external parties. No need for WAR, EAR, .exe installers or tarballs. You can freely choose how your application artifacts are structured. Typically, your runtime will execute your application directly from the file structure produced by your build process.
No need to externalize configuration from your code
When you build an application meant for distribution, you often need to give your consumers a high degree of flexibility in how they configure your application during deployment. This has led to very sophisticated (and complex) frameworks for configuration often involving a large number of XML files. When the behavior of your application can be controlled to a large degree from external configuration, it becomes very hard to test and the expected behavior is harder to understand for the developer (and operator).
Built-for-deployment applications do not have this requirement and therefore you can eliminate all this complexity and instead perform most if not all configuration in your code. By doing configuration in code, you can take advantage of compile-time checks and testing generally becomes much easier. This leads to improved agility and robustness of your delivery process. You can see the trend towards this approach in how newer frameworks like Guice do not use configuration files and older frameworks like Spring and Java EE are moving away from it.
Deployment is a highly automated pipeline process
Because the end-to-end lifecycle of the application is owned and controlled by a single team or organization, you can leverage version control and standard build automation tools to partially or completely automate the delivery process. This is another key factor in increasing agility and robustness of application delivery. The pipeline goes from code commit through a series of testing stages to release of the new version. The automation is often driven by the build system for the project assisted by tools like continuous integration servers.
Enterprise Java applications on Heroku
The Java EE spec defines an application delivery process that is much closer to built-for-distribution than built-for-deployment. It introduces several concepts such as container-independent configuration, packaging, application installation and configuration, that are not necessary when you build applications purely for deployment. Over time, patterns and best practices have emerged for designing built-for-deployment applications that uses all the same Java EE APIs but without using the packaging, distribution, and configuration parts. These practices include
- The application is not deployed to an application server. Instead it bootstraps itself and configures all necessary services in the application code. It may or may not use an IoC container to assist with this configuration. The Jetty web server has been at the forefront of this approach. But Tomcat is also embeddable
- Libraries like the Servlet stack, JDBC drivers, JSP and other Java EE libraries are linked into the application using dependency management as opposed to being provided by the container.
- External configuration files are minimally used because there is really no need.
- Many Java EE services are still used even if there is no container, Servlet, JSP, JDBC and JMS are the most common ones.
Heroku follows this model for Java development and deployment. You can use as many or as few Java EE libraries as you need. But your application is organized for continuous deployment instead of packaging and distribution.
How do applications use Java EE APIs without a container?
Here are some examples of how you can use Java EE APIs:
- You can write Servlets and JSPs by using embedded Tomcat or Jetty library.
- You can use JSF and other rendering frameworks using Mojarra or MyFaces
- You can use javax.mail to build mail functionality on top of external SMTP services like those found in the Heroku Add-ons catalog
- You can use JDBC to connect to databases like Heroku Postgres service or any one of the MySQL add-ons
- You can use Hibernate or DataNucleus JPA to provide an ORM persistence service on top of SQL databases or Database.com
How about the many other things that an application server provides?
The main purpose of application servers is to provide services for deployment and operations of applications. The list of features includes:
- Deployment
- Start/stop/restart
- Deployment of changes
- Clustering (scaling)
- Load-balancing
- Failover and high availability
- Logging
- Service binding
When you deploy an application to a cloud platform like Heroku, the cloud platform is responsible for delivering all these features. You don’t need to “bring your own WebLogic or WebSphere server” in order to get these features. They are a built-in part of the cloud platform.
Let’s take a look at each of these features and how Heroku works compared to a typical Java EE application server.
Deployment
Java EE application server
- Package as WAR or EAR
- Upload to server using a variety of tools
- Optionally perform additional configuration in server console
Heroku
$ git push heroku master
Start/Stop/Restart
Java EE application server
Use the console or an API command to start/stop/restart an application. Application restarts are often error-prone because the server and the application runs in the same JVM.
Heroku
$ heroku restart
Applications run in separate JVMs, each running in its own dyno, completely isolated from each other. This makes restarts very robust with zero side-effects on other applications as opposed to restarting a Java EE application running in the same process as other Java EE applications, sharing memory and other resources.
Deployment of changes
Java EE application server
- Package changes in WAR or EAR
- Upload to server using a variety of tools
- Use console or scripts to perform redeployment following one of several strategies to minimize downtime
Heroku
Redeployment is simply another push of the code to Heroku:
$ git push heroku master
Heroku automatically builds the app, deploys it to new dynos and retires existing dynos.
Clustering (scaling)
Java EE application server
Complex and very app server specific configuration is needed. Clustering often relies on shared file systems like NAS or SAN. Java EE app servers often support stateful sessions with replication across the cluster, which requires additional configuration.
Heroku
Heroku applications are scaled with a single command:
$ heroku ps:scale web=2 worker=4 ...
Heroku takes care of spinning up and down nodes (dynos). Heroku does not support session replication. Heroku has a share-nothing architecture which makes it simple to configure and scale applications, but applications need to be architected for it. The application logic cannot rely on stateful sessions.
Load-balancing
Java EE application server
Load-balancing is most often accomplished using an external load balancer that must be separately configured. It’s usually the responsibility of an operations team to configure load balancers in front of clusters of app servers.
Heroku
Load-balancing is automatic. When you scale your application up and down, the individual nodes (dynos) automatically register and de-register with the routing infrastructure. Requests are routed to your application nodes (dynos) using round-robin. Heroku supports session affinity, but it is not enabled by default.
Failover and high availability
Java EE application server
Application servers are often able to detect nodes that no longer respond and route traffic to other nodes. But they don’t automatically restart nodes. They also cannot respond to problems happening at the OS or hardware level. This requires additional monitoring tools. It is your own responsibility to integrate with your load-balancer so it can detect failed nodes.
Heroku
Heroku detects if a node in an application crashes and then tries to restart it. A restart triggers provisioning of a completely new node which means that problems at the OS level or even hardware level can be overcome with a restart. Heroku separately monitors the hardware nodes and automatically retires broken instances, migrating nodes hosted by those instances to a fresh, new instance.
Logging
Java EE application server
Logging services are configured on the application server. The server can perform some routing of logs as well as log file rotation. Application servers sometimes also have UIs for searching and filtering logs. Applications must be properly configured to bind to and use the logging services configured on the server.
Heroku
Applications are expected to send log events to standard out. Heroku streams log events received on standard out to the Logplex. Application owners can configure any number of “sinks” that consume these streams. There is no configuration required at the application level.
Service binding
Java EE application server
Application servers use a combination of deployment descriptors (multiple), server console and JNDI directory services to configure how applications bind to services.
Heroku
Service configuration is provided as OS environment variables. Applications are responsible for reading these variables and properly configure connectors based on the connection parameters. Configuration files such as spring config files can be used for this, but a simpler approach is to perform the configuration from a bootstrap code segment.
Summary
If you’ve made it this far, you should have a pretty good idea of how Heroku works compared to classic enterprise Java development. The best way to learn more is to try it out: