Change set immutability

An applied change set is immutable. You never edit it — you append a new one. This is the single most important Liquibase rule and the most common cause of a broken development or CI start-up.

The check to run before editing any changelog

When a change to an existing change set — or a file it references, such as a CSV or SQL script — is proposed, stop and determine whether it has already been applied anywhere:

  1. Has this change set ever run (locally, in CI, in staging, in production)? If you cannot prove it has never run, assume it has. A change set merged to a shared branch has almost certainly run in CI.

    SELECT id, author, filename, md5sum, dateexecuted
    FROM DATABASECHANGELOG WHERE id = '<id>';
  2. If it has run, do not edit it. Take one of the alternatives in What to do instead. Editing it changes the stored checksum and fails validation on the next start-up of every environment that already ran it.

  3. If it provably has never run anywhere — a brand-new file in the current uncommitted change, not yet started in any environment — editing is fine; it has no stored checksum yet.

If unsure, treat it as applied. The cost of a needless new change set is one extra file; the cost of editing an applied one is a broken start-up for everyone.

Why editing breaks start-up — and blocks unrelated schema changes

Liquibase update runs in two phases:

  1. Validate — a ValidatingVisitor walks every change set in the whole changelog, recomputes each checksum, and compares it to the MD5SUM stored in DATABASECHANGELOG. All mismatches are collected, and if there is even one, Liquibase throws ValidationFailedException before executing a single change set.

  2. Execute — reached only if validation passed cleanly.

So a stale edit anywhere in the changelog aborts the entire run, including brand-new, unrelated schema changes that sit earlier in the file and would otherwise apply. This is why "I edited some old test data and now my new column won’t deploy" happens: file order is irrelevant — validation is global and upfront.

What to do instead

Append a new change set (the delta pattern)

The default. Add a new change set — usually a new file — that expresses only the change:

  • A wrong or changed column value on existing rows → a new change set with <update> or <sql>.

  • New rows → a new load* / INSERT change set.

  • A column added or altered → a new alter_entity_* file (see Changelog and change set conventions).

The original applied change set stays byte-for-byte untouched; its checksum stays valid.

Make a fixture loader re-runnable

For dev-only fixture data that changes often, the friction of append-only deltas is what tempts people to edit in place. The fix is to make those change sets idempotent and runOnChange="true", so an edit re-runs instead of failing validation:

  • loadDataloadUpdateData primaryKey="…​" (upsert by key), plus runOnChange="true".

  • A pure <update> change set is already idempotent → safe to mark runOnChange="true".

  • A <sql> containing INSERT is not idempotent → do not add runOnChange (a re-run duplicates rows or hits a primary-key collision). Express its amendments as a new delta.

This is a deliberate design choice for fixtures only, covered in detail in Test data management. Never put runOnChange on real schema DDL — schema history must stay immutable.

Escape hatches (recovery only, not routine)

Mechanism When

mvn liquibase:clearCheckSums

Wipes stored checksums so the next run re-baselines them. Use to recover a wedged development database; never against shared or production data you cannot re-create.

mvn liquibase:dropAll / drop the dev schema

Nuke and rebuild. The cleanest reset for disposable development data.

<validCheckSum>ANY</validCheckSum>

Tells Liquibase to accept a changed checksum on one change set. A last resort that hides real drift; prefer a new change set.

Decision table

Situation Action

New file, never applied anywhere

Edit freely

Applied schema change set needs a fix

New alter_* / migrate_* change set; never edit

Applied seed/fixture row value wrong

New <update> / <sql> delta, or convert the loader to loadUpdateData + runOnChange

Need to add fixture rows

New load* change set (upsert), or append to a runOnChange loader’s CSV

Dev database wedged on a checksum error

clearCheckSums, or drop the dev schema

Production / staging failing on a checksum

Do not clearCheckSums blindly — find who edited the applied change set, revert the edit, and add a forward delta