The module and root READMEs carried a https://ankurm.com/ placeholder for the post URL, written before the post existed. Now that it's live, point both at the real permalink.
8.3 KiB
db-migrations-flyway-liquibase
Companion code for Flyway vs Liquibase for Spring Boot 4: Migrations, Rollbacks and
Baselines — every claim in that post traces to a test in this module and a
transcript in docs/output/.
Versions
| Component | Version |
|---|---|
| Spring Boot | 4.1.1 |
Flyway (via spring-boot-starter-flyway) |
12.4.0 |
Liquibase (via spring-boot-starter-liquibase) |
5.0.3 |
| Database | H2 2.4.240 (file-based, AUTO_SERVER=TRUE) |
| JDK | 25 |
Quickstart
mvn -DskipTests package
./scripts/run.sh # default profile: Flyway only, clean startup
./scripts/run.sh both-naive # both enabled, no baseline config — fails to start on purpose
./scripts/run.sh both-fixed # both enabled, Flyway baselined at version 0 — starts cleanly
mvn test # regenerates every transcript in docs/output/
With the app running, hit the diagnostics endpoint to see both tools' bookkeeping tables live:
curl -s localhost:8080/diag/migrations | jq .
Scenarios (Spring profiles)
| Profile | What it demonstrates | Config |
|---|---|---|
| (default) | Flyway-only startup against a fresh database | spring.flyway.enabled=true, spring.liquibase.enabled=false |
both-naive |
Enabling both starters with no other configuration — fails on startup | see chapter 14 |
both-fixed |
Both enabled, Flyway told to baseline at version 0 — coexists correctly | spring.flyway.baseline-on-migrate=true, spring.flyway.baseline-version=0 |
Endpoints
| Endpoint | Purpose |
|---|---|
GET /diag/migrations |
Plain-JDBC dump of both tools' tracking tables and the live table list — delete before shipping (see chapter 15) |
GET /actuator/flyway, GET /actuator/liquibase |
Boot's own actuator endpoints, exposed in application.yml |
Documentation
- The problem and the mental model
- Anatomy of a migration run
- Checksum validation
- Out-of-order migrations
- Repeatable migrations
- Baselining an existing database
- Why there is no undo
- Concurrent startup and locking
- Liquibase: anatomy of an update
- Rollback: auto-generated vs explicit
- Liquibase locking
- The OSS license service
- The FSL license change
- Running both at once
- Production checklist
Captured output
Every number quoted in the post and in the chapters above comes from a committed transcript in
docs/output/, regenerated by mvn test via the Transcript
helper — the tests assert the same numbers they print, so a transcript going stale fails the build:
One file is the exception: 16-liquibase-lock-defaults-javap.txt
isn't produced by mvn test — it's the trimmed javap -p -c -constants output confirming
Liquibase's default lock-poll and lock-wait settings straight from liquibase-core's bytecode
(see chapter 11), captured by hand since there's no JVM assertion
that reads a compiled class's own constant pool.
Findings worth the trip
- Flyway Community's
undo,diff,check,deploy,generate,model,prepareandauthcommands all compile fine and throwFlywayRedgateEditionRequiredExceptiononly at runtime — there is no working rollback in Flyway Community at all (chapter 7). - H2 case-folds unquoted identifiers to uppercase, so an unquoted query against
flyway_schema_history(created and queried by Flyway using quoted lowercase) silently finds nothing — a real bug this module's own diagnostics endpoint had and fixed (chapter 2). - Liquibase's default lock-poll rate is 10 seconds, confirmed by decompiling
GlobalConfiguration's bytecode — a losing instance can wait up to ten seconds for sub-second work, unlike Flyway's near-instant row-lock release (chapter 11). - A genuine Liquibase 5.0.3 defect: reusing the same simple changelog filename for two logically different changelogs causes a phantom "successful" run where the changeset never actually executes (chapter 11).
- Liquibase Community 5.0 shipped under the Functional Source License, not Apache 2.0 — a real, ongoing compliance question for projects like Apache Fineract (ASF LEGAL-721) and Keycloak (GitHub #43391) (chapter 13).
- Enabling both Flyway and Liquibase against one database fails to start by default, and fixing it does not integrate them — it just gets you two independent bookkeepers, each blind to the other's tables (chapter 14).