Liquibase: Rollback

This section documents schema/database evolution using version-controlled migration tools — Liquibase (relational/SQL), Mongock (MongoDB, Java-native) and Flamingock (Mongock’s successor, generalized to any external system) — as described by each tool’s own official documentation: liquibase.org and docs.liquibase.com for Liquibase, docs.mongock.io for Mongock, and docs.flamingock.io for Flamingock — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production.

Mongock is now maintained under the Flamingock GitHub organization (github.com/flamingock/mongock), and Flamingock is positioned as its successor — see Coming from Mongock. Readers starting a new project should default to Flamingock unless they have a specific reason to stay on Mongock.

This section’s bibliography lists the reference material consulted while preparing these pages.

Automatic Rollback

For change types with an obvious, well-defined inverse, Liquibase generates the rollback SQL automatically — createTable rolls back to dropTable, addColumn to dropColumn, createIndex to dropIndex, and so on. No extra authoring is needed:

- changeSet:
    id: create-person-table
    author: alberto
    changes:
      - createTable:
          tableName: person
          columns:
            - column: { name: id, type: bigint, autoIncrement: true, constraints: { primaryKey: true } }

Rolling this back (see Count or Date) issues DROP TABLE person without any explicit <rollback> block, per What is a Rollback?.

Custom Rollback for Change Types With No Inverse

Some change types have no well-defined automatic inverse: dropTable (the data is gone — there is nothing to recreate it from), insert (which row should delete remove, if several look alike?), and raw sql blocks (the tool cannot infer intent from arbitrary SQL text). For these, an explicit <rollback> block is required if the changeset should be reversible at all — Create custom rollback statements:

- changeSet:
    id: drop-legacy-audit-table
    author: alberto
    changes:
      - dropTable:
          tableName: legacy_audit
    rollback:
      - createTable:
          tableName: legacy_audit
          columns:
            - column: { name: id, type: bigint, constraints: { primaryKey: true } }
            - column: { name: event, type: varchar(255) }

The rollback block recreates the table’s shape — but note this restores the schema, not the data that was in it, since dropTable destroyed that. A changeset with no meaningful inverse at all (a one-way data backfill, for instance) can declare rollback: [] or <rollback/> explicitly to document "not reversible" rather than leaving Liquibase to fail when a rollback is attempted.

Rolling Back by Tag, Count or Date

Liquibase can roll back to a named tag, a fixed number of changesets, or a point in time:

liquibase tag release-1.4                 # mark the current state
liquibase rollback release-1.4            # undo everything applied after that tag
liquibase rollback-count 3                # undo the last 3 changesets
liquibase rollback-to-date 2026-01-01     # undo everything applied after that date
liquibase rollback-one-changeset --changeset-id=drop-legacy-audit-table --changeset-author=alberto \
  --changeset-path=db/changelog/legacy-cleanup.yaml   # undo exactly one changeset, out of order

rollback-one-changeset is deliberately narrow — it targets a single changeset by its full identity rather than an ordered range, and is meant for reversing a specific mistake rather than routine deployment rollback. Tagging a known-good state before a release (liquibase tag) is what makes rollback <tag> a reliable way back to it.