Files
asmhatreandClaude Sonnet 5 3908331431 Add db-migrations-flyway-liquibase: Flyway vs Liquibase migrations, rollbacks and baselines on Spring Boot 4.1
Companion code for the Flyway vs Liquibase article: checksum validation, out-of-order
and repeatable migrations, baselining an existing schema, Flyway Community's undo/diff/deploy
stubs, concurrent-startup locking for both tools, Liquibase changeset identity and rollback
(auto-generated vs explicit), a verified Liquibase 5.0.3 filename-caching defect, the new
OSS license service, the FSL license change, and running both tools against one database.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Q6XdRjtsp4862EM44T7i9a
2026-09-15 07:08:57 +00:00

7.4 KiB

15. Production checklist

← 14. Running both at once · Index

Every earlier chapter isolated one behavior. This one is the module's diagnostic exhibit and a closing checklist built directly from what the other fourteen chapters actually found — not generic advice, a list where every line traces back to a specific chapter.

The diagnostic endpoint

MigrationDiagnosticsController is plain JDBC against INFORMATION_SCHEMA plus both tools' tracking tables — no Flyway or Liquibase Java API call anywhere in it. That's deliberate: it shows what actually landed in the database, not what either library's in-memory model believes happened, which is exactly the gap that caused the Liquibase filename defect in chapter 11 (Liquibase's own summary said "successful" while the table stayed empty). Run any profile and hit /diag/migrations:

@GetMapping("/diag/migrations")
public Map<String, Object> migrations() {
    Map<String, Object> result = new LinkedHashMap<>();
    try (Connection conn = dataSource.getConnection()) {
        result.put("tables", tableNames(conn));
        result.put("flyway_schema_history", rows(conn,
                "select \"installed_rank\", \"version\", \"description\", \"type\", \"checksum\", \"success\" "
                        + "from \"flyway_schema_history\" order by \"installed_rank\"", true));
        result.put("databasechangelog", rows(conn,
                "select id, author, filename, orderexecuted, exectype, md5sum "
                        + "from databasechangelog order by orderexecuted", true));
    }
    catch (SQLException ex) {
        result.put("error", ex.getMessage());
    }
    return result;
}

(from MigrationDiagnosticsController.java)

The flyway_schema_history query is quoted lowercase on purpose — chapter 2 covers why an unquoted version of this exact query returns nothing at all against H2's default case-folding. The controller's own javadoc says it plainly: delete this before a real deployment — it has no authorization and dumps raw schema-history rows, including checksums, to anyone who can reach the port.

/diag/migrations plain JDBC, no library API what actually landed not what the library thinks happened Chapter 11's phantom success is exactly the gap this endpoint is built to close — a tool's own "successful" summary and the database's actual state can disagree.

The checklist

Every line below is something this module actually reproduced, not a generic best practice:

  1. Never run unquoted queries against Flyway's tracking table on H2flyway_schema_history is created and queried by Flyway itself using quoted lowercase identifiers, so an unquoted query folds to uppercase and finds nothing (chapter 2).
  2. A checksum mismatch fails startup, full stop — editing an already-applied migration file is caught the instant the app starts, not silently ignored (chapter 3).
  3. Out-of-order migrations fail the whole startup by default, not just the late migration — set outOfOrder=true deliberately if parallel branches will ever land migrations non-sequentially (chapter 4).
  4. baselineVersion defaults to 1, meaning "assume the existing schema already matches V1" — correct only when that's literally true. Get it wrong and Flyway either re-runs migrations that already happened or skips ones that never did (chapter 6, chapter 14).
  5. Flyway Community's undo is a stub that throws FlywayRedgateEditionRequiredException at runtime despite compiling fine — there is no working "undo" without a Redgate license (chapter 7).
  6. Liquibase can only auto-generate a rollback for structural changes like createTable — anything touching data (insert, update, delete) needs an explicit rollback: block written and tested when the changeset is written, not discovered missing during an incident (chapter 10).
  7. Liquibase's default lock-poll rate is 10 seconds — a losing instance in a rolling deploy can sit idle for up to ten seconds waiting on sub-second work, which matters if a readiness probe times out sooner (chapter 11).
  8. Liquibase Community 5.0 ships under the FSL, not Apache 2.0 — ordinary production use is unaffected, but license-compliance tooling that treats "source-available" differently from "open source" will flag it (chapter 13).
  9. Running both tools against one database means two independent bookkeepers, not integration — each is blind to tables the other owns, and the moment they'd need to touch the same object you're outside anything either one tests for (chapter 14).
  10. Delete the diagnostics endpoint before shipping — it has no authorization and exists purely so this module's own claims could be checked against a live server.

Should you even do this — pick one, deliberately

Neither tool is "safer" in the abstract; they fail differently, and this module reproduced both failure shapes directly. Flyway's version-ordered, checksum-validated model is easy to reason about and its Community edition simply has no rollback — plan every migration as forward-only from day one. Liquibase's changeset model supports real rollbacks, but only for the change types it knows how to invert, and its 5.0 license change is a real (if narrow) compliance question some organizations will need to route through their own process. Running both together is a workable bridge during a migration between them, never a permanent architecture — pick one, own it, and use this module to see exactly what "own it" has to account for.

Going deeper