Skip to content
Spring Boot sb databases 3 min read

Liquibase Migrations

Liquibase is a database-agnostic schema migration tool that, like Flyway, version-controls your database. Its distinguishing feature is the changelog: a structured file (XML, YAML, JSON, or SQL) describing changes as portable changesets that Liquibase can translate to each database’s dialect. Spring Boot detects Liquibase on the classpath and applies pending changesets automatically on startup.

Why changelogs

Where Flyway leans on raw SQL scripts, Liquibase encourages declaring changes in a database-independent format. A createTable changeset generates the right AUTO_INCREMENT, BIGSERIAL, or IDENTITY syntax for whichever database you point it at, which is valuable when the same schema must run on MySQL in production and H2 in tests.

Adding the dependency

Add liquibase-core. Spring Boot manages the version.

<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
</dependency>

The master changelog

Spring Boot looks for classpath:db/changelog/db.changelog-master.yaml by default. This master file does not usually contain changes itself; it includes the per-version changelogs in order.

src/main/resources/db/changelog/
├── db.changelog-master.yaml
├── 001-create-products.yaml
└── 002-add-category.yaml
# db.changelog-master.yaml
databaseChangeLog:
  - include:
      file: db/changelog/001-create-products.yaml
  - include:
      file: db/changelog/002-add-category.yaml

Changesets (YAML)

A changeset is the unit Liquibase applies and tracks. Each has an id and author; together with the file path they form a unique key. Once applied, a changeset must not change.

# 001-create-products.yaml
databaseChangeLog:
  - changeSet:
      id: 1
      author: devcraftly
      changes:
        - createTable:
            tableName: products
            columns:
              - column:
                  name: id
                  type: BIGINT
                  autoIncrement: true
                  constraints:
                    primaryKey: true
              - column:
                  name: name
                  type: VARCHAR(120)
                  constraints:
                    nullable: false
              - column:
                  name: price
                  type: DECIMAL(10,2)

Changesets (XML)

The same changeset in XML, which remains the most common Liquibase format in enterprise codebases:

<databaseChangeLog
    xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
        http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.27.xsd">

    <changeSet id="1" author="devcraftly">
        <createTable tableName="products">
            <column name="id" type="BIGINT" autoIncrement="true">
                <constraints primaryKey="true"/>
            </column>
            <column name="name" type="VARCHAR(120)">
                <constraints nullable="false"/>
            </column>
            <column name="price" type="DECIMAL(10,2)"/>
        </createTable>
    </changeSet>
</databaseChangeLog>

Configuration

Tell Spring Boot where the master changelog lives (only needed if you deviate from the default path), and let Liquibase own the schema.

spring:
  liquibase:
    enabled: true
    change-log: classpath:db/changelog/db.changelog-master.yaml
  jpa:
    hibernate:
      ddl-auto: validate

On startup Liquibase creates two bookkeeping tables, DATABASECHANGELOG (every applied changeset, by id/author/file) and DATABASECHANGELOGLOCK (a lock preventing concurrent migrations), then applies any pending changesets.

Warning: Never edit an applied changeset. Liquibase stores an MD5 checksum and fails at startup if it changes. To correct a mistake, add a new changeset.

Liquibase vs Flyway

Both are excellent and widely used. The choice usually comes down to whether you value database portability (Liquibase) or the simplicity of plain SQL (Flyway).

AspectLiquibaseFlyway
Change formatXML, YAML, JSON, or SQLSQL (Java for advanced)
Database portabilityHigh (abstract changesets)Lower (SQL is DB-specific)
Rollback supportBuilt-in (rollback tags)Paid (U__ undo)
Learning curveSteeper (changelog model)Gentle (just SQL)
Tracking tableDATABASECHANGELOGflyway_schema_history
Preconditions / contextsRich supportLimited
Best fitMulti-database, complex rollbacksSQL-first teams, simple flows

Tip: Liquibase changesets support a rollback block, so you can declare how to undo a change. This is a meaningful advantage over Flyway’s free tier, which has no built-in rollback.

Best Practices

  • Keep one changeset per logical change and never modify an applied one.
  • Use a master changelog that includes ordered per-version files for clean diffs.
  • Set ddl-auto: validate so Hibernate checks, but never alters, the schema.
  • Write rollback instructions for changes that are not auto-reversible (raw SQL, data changes).
  • Run migrations against a throwaway database in CI before merging.
Last updated June 13, 2026
Was this helpful?