Deploying Gradle Apps on Heroku
Last updated September 29, 2023
Table of Contents
- Prerequisites
- Overview
- Verify that your build file is set up correctly
- Specify a JDK
- Default web process type
- The Procfile
- How to keep build artifacts out of git
- Build your app and run it locally
- Deploy your application to Heroku
- Using Webapp Runner to deploy WAR files
- Using Grails 3
- Deploying multi-project builds
- Example Applications
This article describes how to take an existing Java app and deploy it to Heroku.
If you are new to Heroku, you might want to start with the Getting Started with Gradle on Heroku tutorial.
Prerequisites
The tutorial assumes that you have:
- an existing Java app that uses Gradle as a build tool.
- a free Heroku account
- the Heroku CLI
- a Java JDK
Overview
The details of Heroku’s Java Support are described in the Heroku Java Support article.
Heroku Java support for Gradle will be applied to applications that contain a build.gradle
file, settings.gradle
file, or a gradlew
file.
If you are using gradlew
, then you must also add your gradle/wrapper/gradle-wrapper.jar
and gradle/wrapper/gradle-wrapper.properties
files to your Git repository. Also, they must not be ignored in your .gitignore
file. If you do not add these files, you will receive an error such as:
-----> executing ./gradlew stage
Error: Could not find or load main class org.gradle.wrapper.GradleWrapperMain
You can add the wrapper files by running these commands:
$ git add gradle/wrapper
$ git commit -m "Gradle Wrapper"
Then deploy again with git push heroku master
.
Verify that your build file is set up correctly
The Gradle buildpack will run different build tasks depending on the frameworks it detects in your app. For Spring Boot, it will run ./gradlew build -x test
. For Ratpack, it will run ./gradlew installDist -x test
. If no known web frameworks are detected, it will run ./gradlew stage
.
If you need to customize your build, you can create a stage
task in your build.gradle
file. If this task is present, Heroku will run it in favor of any framework defaults. Your stage task might look something like this if you’re using Spring Boot:
task stage(dependsOn: ['build', 'clean'])
build.mustRunAfter clean
You can also override the default task by setting the GRADLE_TASK
config var like so:
$ heroku config:set GRADLE_TASK="build"
If the framework you’re using does not automatically vendor dependencies for you, then your build.gradle
file should include a Copy
task that tells Gradle to copy the jar files that your app depends on to the build/libs
directory. This way, they are put into the slug, and the .m2
directory can be removed from the slug. It should look something like this:
task copyToLib(type: Copy) {
into "$buildDir/libs"
from(configurations.compile)
}
stage.dependsOn(copyToLib)
If you want to avoid running tests during your Heroku build, you can either provide the -x test
option in your custom config var, or you can use the following code in your build.gradle
to disable the test
task during the execution of the stage
task:
gradle.taskGraph.whenReady {
taskGraph ->
if (taskGraph.hasTask(stage)) {
test.enabled = false
}
}
You can change stage
in this example to fit whatever task you have configured Heroku to run.
Specify a JDK
Optionally, you can specify a JDK. For more information, see Specifying a Java version.
Default web process type
The Gradle buildpack will automatically detect the use of the Spring Boot and Ratpack web frameworks. For Spring Boot, it will create a web process type with the following command:
java -Dserver.port=$PORT $JAVA_OPTS -jar build/libs/*.jar
For Ratpack, the buildpack will use a command in the following form:
build/install/${rootProject.name}/bin/${rootProject.name}
Where ${rootProject.name}
is the value configured in your Gradle build (often in your settings.gradle
).
If you need to customize or override the default web
command, you must create a Procfile
.
The Procfile
A Procfile is a text file in the root directory of your application that defines process types and explicitly declares what command should be executed to start your app. Your Procfile
will look something like this for Spring Boot:
web: java -Dserver.port=$PORT $JAVA_OPTS -jar build/libs/demo-0.0.1-SNAPSHOT.jar
Both of these examples declare a single process type, web
, and the command needed to run it. The name “web” is important. It declares that this process type will be attached to the HTTP routing stack of Heroku, and receive web traffic when deployed.
How to keep build artifacts out of git
Prevent build artifacts from going into revision control by creating a .gitignore
file. Here’s a typical .gitignore
file:
build
.gradle
.idea
out
*.iml
*.ipr
*.iws
.DS_Store
Build your app and run it locally
To build your app locally do this:
Git Bash
application to open a command shell on Windows. A shortcut for this application was added to your desktop as part of the CLI installation.$ ./gradlew stage
$ heroku local --port 5001
Or on Windows:
$ gradlew.bat stage
$ heroku local --port 5001
Your app should now be running on http://localhost:5001/.
Deploy your application to Heroku
After you commit your changes to git, you can deploy your app to Heroku.
$ git add .
$ git commit -m "Added a Procfile."
$ heroku login
Enter your Heroku credentials.
...
$ heroku create
Creating arcane-lowlands-8408... done, stack is heroku-18
http://arcane-lowlands-8408.herokuapp.com/ | git@heroku.com:arcane-lowlands-8408.git
Git remote heroku added
$ git push heroku master
...
-----> Gradle app detected
...
-----> Launching... done
http://arcane-lowlands-8408.herokuapp.com deployed to Heroku
To open the app in your browser, type heroku open
.
Using Webapp Runner to deploy WAR files
You can deploy precompiled WAR files using our WAR deployment tools, but if your WAR file is compiled on Heroku, you’ll need to include Tomcat Webapp Runner by adding the following configuration to your build.gradle
:
dependencies {
compile 'com.github.jsimone:webapp-runner:8.5.11.3'
}
task stage() {
dependsOn clean, war
}
war.mustRunAfter clean
task copyToLib(type: Copy) {
into "$buildDir/server"
from(configurations.compile) {
include "webapp-runner*"
}
}
stage.dependsOn(copyToLib)
Then modify your Procfile
to look like this:
web: java -jar build/server/webapp-runner-*.jar build/libs/*.war
Finally, redeploy your app with git push
.
Using Grails 3
Grails 3, when built as a WAR file, requires Webapp Runner as described above, and the following configuration in your build.gradle
:
tasks.stage.doLast() {
delete fileTree(dir: "build/distributions")
delete fileTree(dir: "build/assetCompile")
delete fileTree(dir: "build/distributions")
delete fileTree(dir: "build/libs", exclude: "*.war")
}
This will run the appropriate tasks for the stage
command and remove unneeded build artifacts from the slug. You can see a complete example of this in the Grails 3 example app’s build.gradle file.
Your Procfile
must then contain something like the following:
web: cd build ; java -Dgrails.env=prod -jar ../build/server/webapp-runner-*.jar --expand-war --port $PORT libs/*.war
This will move into the build
directory, which Grails requires, and run the Tomcat Webapp-Runner launcher with your WAR file.
Deploying multi-project builds
There are different types of multi-project Gradle builds, and how you deploy them to Heroku depends on how they are meant to be run. The descriptions below cover the most common patterns.
Multiple process types in the same project
When the sub-projects in your Gradle repo includes mutliple process types that correspond to different Heroku dyno types (for example, a web
project and a worker
project in the same repo), you can configure Gradle to build and run them both when Heroku executes the stage
task.
First, create a gradle/heroku/stage.gradle
file in your repo with the code that defines the stage
task for all sub-projects. For example:
task stage(dependsOn: ['clean', 'shadowJar'])
Then apply the stage.gradle
in your root project’s build.gradle
like this:
subprojects {
// ...
apply from: file("$rootProject.projectDir/gradle/heroku/stage.gradle")
}
Finally, create a Procfile
with commands that run the artifacts corresponding to your sub-projects. For example:
web: java -jar build/libs/server-all.jar
worker: java -jar build/libs/worker-all.jar
A complete example of an application configured this way can be found in the gradle-multi-project-example Github project.
Multiple application types in the same project
In some cases, your Gradle project will contain sub-projects that should not run on Heroku. For example, your repo might include an Android app that shares code with your server application. In this case, you only want to build the server-side project when you deploy to Heroku. In your settings.gradle
, you can exclude the Android sub-project by wrapping its include
statement in an if
block that checks a flag.
if (INCLUDE_ANDROID == "true") {
include 'android', 'android:app'
}
Then you can set your GRADLE_TASK
on Heroku such that the flag is turned off:
$ heroku config:set GRADLE_TASK="-Dorg.gradle.project.INCLUDE_ANDROID=false :backend:stage"
This assumes you have a sub-project called backend
with a stage
task. When Heroku receives your code, it will run the task defined by the GRADLE_TASK
config var.
Multiple web server apps in the same project
When your Gradle repo consists of multiple web server applications in the same project, you will need to deploy your the project to multiple Heroku apps. In this case, you’ll need a Procfile
that is flexible enough to work for both applications by parameterizing it with config variables. For example, your Procfile
might look like this:
web: $WEB_CMD
Then you can set WEB_CMD
config var on each Heroku app with the appropriate command used to start the sub-project. For example:
$ heroku config:set WEB_CMD="java -jar build/libs/server-1.jar" -a sushi-1
$ heroku config:set WEB_CMD="java -jar build/libs/server-2.jar" -a sushi-2
If you are deploying directly to the apps with git push
you must associate them with your Gradle repo by running heroku create
with the --remote
flag. Alternatively, you can use Heroku Pipelines in combination with a “master” app that has zero dynos . The master app will build all artifacts in the project when you deploy. Then you can promote the slug upstream to multiple apps, each with different WEB_CMD
config vars. In this way, you only need to build the Gradle artifacts once.