Database Migrations in Java: A Complete Overview of Tools and Libraries
Modern Java application development rarely goes without working with relational databases. As a project evolves, the database structure (schema) inevitably changes: new tables are added, existing columns are modified, indexes or constraints are introduced. Manually executing SQL scripts across different environments (development, testing, production) leads to errors, desynchronization, and wasted time. The solution to this problem is database migrations. This is an automated way of versioning and applying schema changes.
In the Java ecosystem, there are two dominant tools for managing migrations: Flyway and Liquibase. Both are mature, open-source solutions, but they have different approaches to describing changes. In this article, we will thoroughly examine both tools, their capabilities, usage examples, and help you choose the right one for your project.
Why are database migrations needed?
The main advantages of using migrations:
- Schema versioning: each change is a separate version that can be tracked in a version control system (Git).
- Reproducibility: any developer can deploy the current database with a single command.
- Deployment automation: migrations are executed automatically when the application starts or in CI/CD pipelines.
- Safety: protection against accidental re-application of changes.
- Rollback: most tools support rolling back migrations.
Flyway: Simplicity and Conventions
Flyway adheres to the philosophy of "convention over configuration". Migrations are regular SQL files named according to a strict template: V{number}__{description}.sql. For example: V1__create_users.sql, V2__add_email_column.sql. Flyway tracks applied migrations in a special table flyway_schema_history.
Example of using Flyway
1. Add the dependency to pom.xml (Maven):
<dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> <version>9.22.3</version></dependency>2. Create a migration file src/main/resources/db/migration/V1__create_users.sql:
CREATE TABLE users ( id BIGSERIAL PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL UNIQUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);3. Configure and run the migration in Java code:
import org.flywaydb.core.Flyway;
public class DatabaseMigrator { public static void main(String[] args) { Flyway flyway = Flyway.configure() .dataSource("jdbc:postgresql://localhost:5432/mydb", "user", "password") .load(); flyway.migrate(); }}After execution, the database will have a users table and a service table flyway_schema_history with a record of version 1.
Liquibase: Flexibility and Formats
Liquibase offers a more flexible approach. Changes can be described in three formats: SQL, XML, YAML, or JSON. The main concept is a changelog, which contains a set of changesets. Each changeset has a unique identifier (id) and an author. Liquibase supports more complex scenarios: preconditions, contexts, and labels.
Example of using Liquibase
1. Add the dependency to pom.xml:
<dependency> <grou