Running Database Migrations for Java Apps
Last updated June 05, 2020
Table of Contents
Most database-backed applications will need to change their database schema during the course of operations. These changes are often controlled by a process called migrations or evolutions.
In this article, you’ll learn how to use two tools, Liquibase and Flyway, to run database migrations for a Java application on Heroku.
Using Liquibase
There are many ways to run Liquibase. It provides a Maven plugin, a standalone command-line tool, a Hibernate plugin, and a Spring Bean. In this article, we’ll discuss the best mechanisms for use on Heroku.
Running Liquibase automatically with Spring
Liquibase provides a very convenient SpringLiquibase bean that automatically runs your migrations on startup if they are in the correct location.
If you are using Spring Boot, you only need to include the liquibase-core
dependency and put your change log in src/resources/db/changelog/db.changelog-master.yaml
, as demonstrated in this Spring and Liquibase sample application.
Upon startup, you will see something like this in your logs:
2015-10-20T02:00:51.937179+00:00 app[web.1]: 2015-10-20 02:00:51.936 INFO 3 --- [main] liquibase : Successfully acquired change log lock
2015-10-20T02:00:55.419669+00:00 app[web.1]: 2015-10-20 02:00:55.419 INFO 3 --- [main] liquibase : Reading from public.databasechangelog
2015-10-20T02:00:55.571104+00:00 app[web.1]: 2015-10-20 02:00:55.555 INFO 3 --- [main] liquibase : Successfully released change log lock
However, running migrations at startup does add to your app’s boot time and may cause you to exceed the boot-timeout limit imposed by Heroku. If so, you may find that running the migrations in the Heroku release phase is preferable.
Running Liquibase with the Maven Plugin
The Liquibase migrations can be run in Heroku release phase using the Liquibase Maven Plugin by adding the following plugin configuration to your pom.xml
:
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<configuration>
<changeLogFile>src/main/resources/db/changelog/db.changelog-master.yaml</changeLogFile>
<url>${env.JDBC_DATABASE_URL}</url>
</configuration>
</plugin>
Next, make sure you’re using the Maven Wrapper in your project. If you’re not already, you can add it by running:
$ mvn -N io.takari:maven:wrapper
$ git add mvnw .mvn
$ git commit -m "Added maven wrapper"
With this in place, you can add a process entry to your Procfile
that
will run the migrations in the release phase of deployment:
release: ./mvnw liquibase:update
However, this will require downloading all of the Liquibase dependencies each time release phase runs because the Maven .m2
cache is not included in your app’s slug. If that overhead is prohibitive you may want to run Liquibase without Maven.
Running Liquibase with the command-line tool
Your Liquibase migrations can be run in Heroku release phase using the Liquibase command-line tool. To use this tool, you’ll need to include the Liquibase JAR file in your slug by adding the following plugin configuration to your Maven pom.xml
:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals><goal>copy</goal></goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>3.4.1</version>
<destFileName>liquibase.jar</destFileName>
</artifactItem>
<artifactItem>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.13</version>
<outputDirectory>${project.build.directory}/dependency/lib</outputDirectory>
</artifactItem>
<artifactItem>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4-1204-jdbc41</version>
<destFileName>postgres.jar</destFileName>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
This will copy the JAR file to target/dependency/liquibase.jar
during the Maven package
goal. With this in place, you can add a process entry to your Procfile
that will run the migrations in the release phase of deployment:
release: java -jar target/dependency/liquibase.jar --changeLogFile=src/main/resources/db/changelog/db.changelog-master.yaml --url=$JDBC_DATABASE_URL --classpath=target/dependency/postgres.jar update
Liquibase is not the only migration engine for Java. You can also use Flyway.
Using Flyway
Flyway provides several mechanisms for running migrations including a command-line tool, Maven plugin, Gradle plugin and an SBT plugin. In this article, we’ll discuss the best mechanisms for use on Heroku.
Running Flyway automatically with Spring
You can run Flyway automatically by including the flyway-core
dependency in your app and putting your migration scripts in src/main/resources/db/migration/
, as demonstrated in this Spring and Flyway sample application.
However, running migrations at startup does add to your app’s boot time and may cause you to exceed the boot-timeout limit imposed by Heroku. If this is the case, you may find that running the migrations from Heroku release phase preferable.
Running Flyway with the Maven Plugin
You can also run the Flyway Maven Plugin from Heroku’s release phase. First, add the plugin to you pom.xml
:
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<configuration>
<url>${env.JDBC_DATABASE_URL}</url>
</configuration>
</plugin>
Next, make sure you’re using the Maven Wrapper in your project. If you’re not already, you can add it by running:
$ mvn -N io.takari:maven:wrapper
$ git add mvnw .mvn
$ git commit -m "Added maven wrapper"
With this in place, you can add a process entry to your Procfile
that
will run the migrations in the release phase of deployment:
release: ./mvnw flyway:migrate
However, this will require downloading all of the Flyway dependencies each time release phase runs because the Maven .m2
cache is not included in your app’s slug. If that overhead is prohibitive you may want to run Flyway without Maven.
Running Flyway with the Java API
To use the Flyway Java API, create a simple class with a main method, such as this:
package sample.flyway;
import org.flywaydb.core.Flyway;
public class Migrations {
public static void main(String[] args) throws Exception {
Flyway flyway = new Flyway();
flyway.setDataSource(System.getenv("JDBC_DATABASE_URL"),
System.getenv("JDBC_DATABASE_USERNAME"),
System.getenv("JDBC_DATABASE_PASSWORD"));
flyway.migrate();
}
}
Then copy the flyway JAR file into your slug file by adding this plugin configuration to your pom.xml
file:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals><goal>copy</goal></goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>3.2.1</version>
<destFileName>flyway.jar</destFileName>
</artifactItem>
<artifactItem>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4-1204-jdbc41</version>
<destFileName>postgres.jar</destFileName>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
This will copy the JAR file to target/dependency/flyway.jar
during the Maven package
goal. With this in place, you can add a process entry to your Procfile
that
will run the migrations:
release: java -cp target/spring-boot-sample-flyway-1.0.0.jar:target/dependency/* sample.flyway.Migrations
This will run the migrations during the Heroku release phase. You can also run the migrations manually with this command:
$ heroku run release
Further Reading
For more information, see the official documentation pages for each of these open source projects:
And you can read more about Connecting to Relational Databases on Heroku with Java in the Dev Center.