Continuous delivery with Heroku

info

Continuous Delivery (or Continuous Deployment, or CD) is a software engineering approach in which software functionalities are delivered frequently through automated deployments.

We will be using Heroku, a platform as a service (PaaS) based on a managed container system, with integrated data services, for deploying and running software applications. Heroku is primed for continuous delivery.1 It is, in a nutshell, a cloud application platform that lets you deploy your server online. By taking care of most things related to deployment, it makes it easy to get your application up and running.

Deploying SparkJava on Heroku

Before we get started, you must:

Build a simple SparkJava App

Create a new Gradle Java project herokuDemo in IntelliJ and add the following dependencies to it

implementation 'org.slf4j:slf4j-simple:2.0.0-alpha1'
implementation 'com.sparkjava:spark-core:2.9.2'

Create the following Java class:

public class Application {
final static int PORT = 7000;
public static void main(String[] args) {
port(PORT);
get("/", (req, res) -> "Hi Heroku!");
}
}

You can run this application and point your browser to http://localhost:7000/ to see the Hi Heroku! message!

Prepare to deploy

We want to package our SparkJava application as a single executable file that can be run on Heroku. We are going to make a fat JAR file2 for this purpose. A fat JAR contains your application and all the dependencies needed to run your application.

Gradle is able to package your Java application into a JAR file but, by default, it does so without including the project dependencies. We can overwrite the default behavior by adding a few lines to the project's build.gradle:

jar {
manifest {
attributes 'Main-Class' : 'Application'
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}

Now you can run Gradle's jar task: open the terminal at the root directory of your Java project and type the following command:3

$ ./gradlew build jar

Once the process is finished, you can find your newly packaged JAR file in the build/libs directory. Verify that the archive is valid by running the following command:

$ java -jar build/libs/HerokuDemo-1.0-SNAPSHOT.jar

Heroku Gradle Plugin

To deploy Gradle based JVM applications directly to Heroku, we will use a heroku-gradle plugin. Add the plugin to your build.gradle:

plugins {
id "com.heroku.sdk.heroku-gradle" version "2.0.0"
}

Moreover, you need to add a configuration for the heroku-gradle plugin; add the following to your build.gradle:

heroku {
jdkVersion = 1.8
processTypes(
web: "java -jar build/libs/HerokuDemo-1.0-SNAPSHOT.jar"
)
}

Making SparkJava Listen on the Correct Port

When we deployed SparkJava locally, we used the 7000 port on localhost. Heroku assigns your application a new port every time you deploy it, so we have to get this port and tell SparkJava to use it:

final int PORT = 7000;
private static int getHerokuAssignedPort() {
String herokuPort = System.getenv("PORT");
if (herokuPort != null) {
return Integer.parseInt(herokuPort);
}
return PORT;
}
public static void main(String[] args) {
port(getHerokuAssignedPort());
get("/", (req, res) -> "Hi Heroku!");
}
tip

A single app runs in different environments, for example on your development machine when developing the app and on the cloud (e.g. Heroku) in production. Although you run the same exact code, but the environments usually (almost always!) have their own environment-specific configurations. An app’s environment-specific configuration can be stored in environment variables (and not in the app’s source code). In the above code, we get the value of PORT env variable since Heroku automatically sets that env variable value when running an app. Thus, we can use that to decide if the app is currently running on Heroku or not.

Heroku & Git

Heroku is deeply integrated with the Git workflow. However, heroku-gradle plugin that we listed under plugins is used to deploy Gradle-based JVM applications directly to Heroku without pushing to a Git repository.

Deploy

Before deployment, you must create a Heroku app using the Heroku CLI; open the terminal at the root directory of your Java project and type the following command:

heroku login

It will allow you to login to your Heroku account (via your browser if you want). Next, try:

$ heroku create

This will create a new Heroku app with a random unique name (e.g. boiling-stream-95315) for your application.

info

You "create" the app once, before deploying for the first time.

tip

You must have Heroku CLI installed in order for these commands to work!

Next, open your build.gradle file and add the following under heroku:

appName="boiling-stream-95315"
info

Note that you must replace the above using whatever name Heroku has assigned to your app.

Then, run the following command:4

$ ./gradlew build deployHeroku

If all goes well, you will get a message that looks like this

> Task :deployHeroku
-----> Packaging application...
- including: build\classes\java\main\Application.class
- including: build\libs\HerokuDemo-1.0-SNAPSHOT.jar
- including: build\tmp\jar\MANIFEST.MF
-----> Creating build...
- file: C:\Users\Ali\AppData\Local\Temp\heroku-deploy1390958216981099847source-blob.tgz
- size: 2MB
-----> Uploading build...
- success
-----> Deploying...
remote:
remote: -----> heroku-gradle app detected
remote: -----> Installing JDK 1.8... done
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote:
remote: -----> Compressing...
remote: Done: 53.6M
remote: -----> Launching...
remote: Released v10
remote: https://boiling-stream-95315.herokuapp.com/ deployed to Heroku
remote:
-----> Done
Deprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.
Use '--warning-mode all' to show the individual deprecation warnings.
See https://docs.gradle.org/6.3/userguide/command_line_interface.html#sec:command_line_warnings
BUILD SUCCESSFUL in 12s
1 actionable task: 1 executed
Publishing a build scan to scans.gradle.com requires accepting the Gradle Terms of Service defined at https://gradle.com/terms-of-service. Do you accept these terms? [yes, no] yes
Gradle Terms of Service accepted.
caution

Note when deploying, it may ask you a yes/no question (as indicated above): you need to say yes to that!

You can point your browser to the URL of your Heroku application. For example, the example above is deployed at https://boiling-stream-95315.herokuapp.com/.


  1. Read more about Continuous Delivery on Heroku at https://www.heroku.com/continuous-delivery.↩
  2. JAR stands for Java ARchive. It is used for aggregating many Java files into one. It is the preferred way to bundle a Java application. Read more on Oracle's website.↩
  3. You can also run this task in IntelliJ from the Gradle Tool Window↩
  4. You can configure IntelliJ to run this task by following the instructions here.↩