Guide
Diagnosing Liquibase Rollback Failures When update Appears Successful
A step‑by‑step diagnostic guide to identify why Liquibase reports a successful update but the database schema remains unchanged or fails to roll back.
Published by Tasadduq Burney
04 Jul 2025, 13:16 UTC
3 min111.1K views0

Recognizable condition
Liquibase reports a successful update (or updateSQL) but the database schema shows no change, or a subsequent rollbackCount 1 leaves the schema in its original state. This indicates that the targeted changeSet was neither applied nor rolled back as expected.
Cause/diagnostic table
| Possible cause | What to look for |
|---|---|
| Missing or empty <rollback> tag | The changeSet contains no inverse logic, so rollback does nothing. |
| Custom <sql> without matching rollback statements | A <sql> block runs forward SQL but the <rollback> lacks a corresponding <sql>. |
Use of updateSQL mode | Changes are only logged to stdout; no DDL is executed against the database. |
| Preconditions (<preConditions>) that evaluate to false during rollback | Conditions based on mutable data (e.g., row counts) block the inverse operation. |
runOnChange="true" causing the changeSet to be skipped after first run | Liquibase treats the changeSet as already executed and skips it on subsequent runs, including rollback attempts. |
Ordered checks
- Inspect the changeSet – Open the changelog (XML/JSON/YAML) and verify that a <rollback> element exists and contains valid inverse operations (e.g., a <dropTable> matching a <createTable>).
- Check the command used – If you ran
liquibase updateSQL, re‑run the same command without theupdateSQLflag to actually apply the changes. - Examine preconditions – Temporarily comment out or remove any <preConditions> inside the changeSet and rerun
updateandrollbackCount 1to see if they were blocking execution. - Look for run attributes – Search for
runOnChangeorrunAlways. IfrunOnChange="true"is present, consider changing it torunAlways="true"or creating a new changeSet for the modification. - Confirm Liquibase version support – Ensure you are using Liquibase 4.x and that the rollback syntax you employ (e.g., nested <delete> inside <rollback>) is supported; consult the release notes for your exact version.
Fixes tied to findings
- Add missing <rollback> – Provide inverse DDL. Example for a table creation:
<changeSet id="20261012-01" author="dev"> <createTable tableName="audit_log"> <column name="id" type="BIGINT" autoIncrement="true"> <constraints primaryKey="true" nullable="false"/> </column> <column name="event" type="VARCHAR(255)"/> </createTable> <rollback> <dropTable tableName="audit_log" /> </rollback> </changeSet> - Match custom SQL with rollback SQL – If you used a <sql> block, add a corresponding <rollback><sql>:
<changeSet id="20261012-02" author="dev">
<sql>INSERT INTO config (key, value) VALUES ('feature_x', 'enabled');</sql>
<rollback>
<sql>DELETE FROM config WHERE key = 'feature_x';</sql>
</rollback>
</changeSet>
- Switch from updateSQL to update – Run
liquibase --changeLogFile=db/changelog.xml updateto apply changes to the database. - Adjust or remove problematic preconditions – Replace mutable checks with static ones or move the logic to a precondition that evaluates true during both update and rollback.
- Address runOnChange skipping – Change the attribute to
runAlways="true"or create a new changeSet with a unique ID for the modification.
Escalation criteria
If after applying the above fixes the schema still does not change or rollback, consider:
- Enabling debug logging (
--logLevel=debug) to see which changeSets Liquibase evaluates and why they are skipped. - Checking the
DATABASECHANGELOGtable for unexpectedEXECTYPEvalues (e.g.,EXECUTEDvsFAILED). - Verifying that the correct changelog path and context are being used (no accidental override via
--contextsor--labels). - Consulting the Liquibase version‑specific release notes for known bugs affecting rollback in your 4.x release.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.