Understanding Liquibase Rollback: Requirements, Boundaries, and Safety Checks
Learn how Liquibase’s automatic rollback works, what trust boundaries it enforces, and how to verify safe rollbacks in practice.
16 Jan 2026, 22:34 UTC

Requirements for Reliable Rollback
Liquibase can roll back a changeSet automatically when the change is invertible. The requirement is that the change type implements the RollbackInterface and does not cause irreversible data loss.
Minimal Suitable Design
The smallest design that gives you rollback safety consists of:
- A changelog file (XML, YAML, or JSON) containing changeSets with optional explicit
<rollback>sections. - The
DATABASECHANGELOGtable that records each executed changeSet and its checksum. - Liquibase runtime that validates checksums before executing rollback.
Trust and Data Boundaries
The checksum stored in DATABASECHANGELOG forms a trust boundary. Before a rollback, Liquibase recomputes the checksum of the changeSet as it appears in the version‑controlled file and compares it to the stored value. If they differ, rollback is aborted to prevent acting on a drifted database.
Operational Checks
- Run a trial rollback in a staging copy:
liquibase --changeLogFile=src/main/resources/db/changelog.xml --url=jdbc:postgresql://staging-db:5432/app --username=app_user --password=*** rollbackCount 1 - Inspect the generated SQL (Liquibase prints it to stdout) to confirm it matches the expected inverse operation.
- After execution, query
SELECT * FROM DATABASECHANGELOG WHERE DATEEXECUTED IS NOT NULL;to verify the row for the rolled‑back changeSet has been removed. - Check the schema directly (e.g.,
\d mytablefor PostgreSQL) to ensure the column or object is gone.
Failure Modes
- Checksum mismatch: Liquibase aborts with a validation error and leaves the database unchanged.
- Explicit rollback script failure: If a user‑provided
<rollback>block throws an error, theDATABASECHANGELOGentry is not updated, potentially leaving the database in a partially rolled‑back state. - Non‑invertible change without explicit rollback: Liquibase skips rollback, leaving the change applied and the changelog entry intact.
Design‑Change Triggers
You may alter the default rollback behaviour by:
- Setting
liquibase.haltOnError=trueto stop execution on any error, including rollback failures. - Using
futureRollbackSQLto preview the DDL that would be generated for a rollback without executing it. - Implementing a custom change type that implements
RollbackInterfacefor specialised logic (e.g., encrypting/decrypting columns).
Concrete Example
Consider a simple XML changeSet that adds a nullable column:
<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.xsd">
<changeSet id="20261010-01" author="alice">
<addColumn tableName="users">
<column name="middle_name" type="varchar(50)">
<constraints nullable="true"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
After applying this changeSet with liquibase update, you can verify the column exists. To roll it back:
liquibase --changeLogFile=db/changelog.xml --url=jdbc:postgresql://test-db:5432/testdb --username=test_user --password=test_pass rollbackCount 1
Liquibase will generate the inverse ALTER TABLE users DROP COLUMN middle_name; SQL, execute it, and delete the corresponding row from DATABASECHANGELOG. You can confirm the column is gone by running SELECT column_name FROM information_schema.columns WHERE table_name='users' AND column_name='middle_name'; which should return no rows.
Limitations and Practical Verification
Automatic rollback only works for change types that Liquibase knows how to invert. For custom SQL, dropColumn where data loss is possible, or any change that modifies existing data, you must supply an explicit <rollback> block or set rollbackOnError=false to skip rollback. To verify that your explicit rollback works:
- Add a changeSet with a custom SQL update and a matching
<rollback>block. - Apply it with
liquibase update. - Run
liquibase rollbackCount 1. - Check that the data is reverted as expected and that the
DATABASECHANGELOGentry is removed.
If the rollback block fails, the DATABASECHANGELOG row stays, signalling a partial state; you must then intervene manually.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.