DEV Community

Marcin Parśniak
Marcin Parśniak

Posted on

Liquibase in Spring Boot – Developer's Guide

Liquibase in Spring Boot – Developer's Guide

Managing database schema changes can seem simple at first… until it isn’t. A quick ALTER TABLE here and there might work with one developer, but once you have multiple environments—dev, staging, prod—things get messy fast.

This is where Liquibase comes in.

In this guide, I’ll walk you through:

  • What Liquibase is and why it matters
  • Setting it up in Spring Boot
  • Writing migrations safely
  • Managing different environments

What is Liquibase?

Liquibase is essentially Git for your database schema. Instead of running SQL scripts manually and hoping everyone runs them correctly, you define changes in a structured format and Liquibase tracks which ones have been applied.

Example XML changeset:

<changeSet id="add-phone-number" author="dev">
    <addColumn tableName="users">
        <column name="phone_number" type="varchar(20)"/>
    </addColumn>
</changeSet>
Enter fullscreen mode Exit fullscreen mode

Liquibase keeps track of applied migrations in a table called DATABASECHANGELOG, ensuring:

  • Changes are applied only once
  • Order is maintained
  • Rollbacks are possible

Why use Liquibase with Spring Boot?

Some key benefits:

  1. Consistent environments – dev, staging, and prod stay in sync
  2. Migration history – see who changed what and when
  3. Automatic migrations – Spring Boot can run them on startup
  4. Rollbacks – undo changes safely if needed

Adding Liquibase to your project

Maven:

<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Gradle:

implementation 'org.liquibase:liquibase-core'
Enter fullscreen mode Exit fullscreen mode

Spring Boot will automatically detect Liquibase and run migrations.


Configuration

In application.yml (safe YAML formatting):

spring:
  liquibase:
    change-log: "classpath:/db/changelog/changelog.xml"
    contexts: "dev"
Enter fullscreen mode Exit fullscreen mode

Typical project structure:

resources
 └ db
    └ changelog
        └ changelog.xml
Enter fullscreen mode Exit fullscreen mode

Master changelog with multiple changesets

Instead of separate XML files, you can put all changesets in a single file: changelog.xml

<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-3.8.xsd">

    <changeSet id="create-users" author="you">
        <createTable tableName="users">
            <column name="id" type="bigint">
                <constraints primaryKey="true"/>
            </column>
            <column name="email" type="varchar(255)">
                <constraints nullable="false"/>
            </column>
            <column name="created_at" type="timestamp"/>
        </createTable>
    </changeSet>

    <changeSet id="add-email-index" author="you">
        <createIndex indexName="idx_users_email" tableName="users">
            <column name="email"/>
        </createIndex>
    </changeSet>

    <changeSet id="add-phone-column" author="you">
        <addColumn tableName="users">
            <column name="phone_number" type="varchar(20)"/>
        </addColumn>
        <rollback>
            <dropColumn tableName="users" columnName="phone_number"/>
        </rollback>
    </changeSet>

    <changeSet id="insert-test-data" author="you" context="you">
        <insert tableName="users">
            <column name="id" valueNumeric="1"/>
            <column name="email" value="test@example.com"/>
        </insert>
    </changeSet>

</databaseChangeLog>
Enter fullscreen mode Exit fullscreen mode

Using SQL instead of XML

You can also write all changesets in a single formatted SQL file:

-- liquibase formatted sql
-- changeset dev:create-users
CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    created_at TIMESTAMP
);

-- changeset dev:add-email-index
CREATE INDEX idx_users_email ON users(email);

-- changeset dev:add-phone-column
ALTER TABLE users ADD COLUMN phone_number VARCHAR(20);
-- rollback: ALTER TABLE users DROP COLUMN phone_number;
Enter fullscreen mode Exit fullscreen mode

Best practices

  1. Never edit executed migrations – migrations are immutable
  2. One changeset = one change – separate tables, columns, and indexes
  3. Number your files if using multiple files – avoids Git conflicts
  4. Test locally – drop the DB, run the app, ensure migrations work from scratch
  5. Be careful on production – avoid ALTER COLUMN TYPE, DROP COLUMN, or NOT NULL on large tables

Liquibase vs Flyway

Feature Liquibase Flyway
XML/YAML/JSON
SQL
Rollback limited

Flyway is simpler but Liquibase is more flexible for enterprise setups.


Conclusion

Liquibase is a lifesaver for keeping database schema consistent across environments. Stick to the golden rules:

  • immutable migrations
  • one change per changeset
  • test migrations locally

Thanks for reading!

Top comments (0)