Migrating From Flyway to Liquibase Without Downtime
Your team has decided to move to Liquibase — you want its rollback changesets, contexts, and database-agnostic changelogs — but production already runs on Flyway with a flyway_schema_history table recording two hundred applied versions. You cannot drop that history and start clean: doing so risks Liquibase re-running migrations that are already live, or worse, blocking your next deploy entirely while it tries to apply versions that exist only in Flyway’s bookkeeping. The decision that lands engineers here is “how do we adopt the new tool without a maintenance window?” The answer is an incremental cutover: baseline Liquibase against the live schema, import the existing migrations as a pre-applied changelog, run both tools in a dual-tracking period, then retire Flyway. No statement in this procedure rewrites a table or holds a lock.
Symptom / Error Signatures
The failure modes that make a naive switch dangerous show up the moment Liquibase first runs against a Flyway-managed database:
- Liquibase attempts to re-apply schema that already exists:
liquibase.exception.DatabaseException: ERROR: relation "orders" already exists(PostgreSQL) or... Table 'orders' already exists(MySQLERROR 1050). Table 'DATABASECHANGELOG' doesn't existfollowed by Liquibase creating it and treating every changeset as unrun.- Checksum or ordering confusion because Flyway’s
V2__add_index.sqlversioning and Liquibase’sid/authorchangeset identity are not interchangeable. - A blocked deploy: the next Flyway migration fails its repeatable validation because someone hand-edited the
flyway_schema_historytable while experimenting with the switch. - Two tracking tables (
flyway_schema_historyandDATABASECHANGELOG) disagreeing about what is applied, leaving no single source of truth.
The deeper tradeoffs between the two tools — versioned versus changeset model, SQL versus declarative changelog — are covered in Flyway vs Liquibase: Choosing the Right Migration Tool; this page assumes you have already chosen and need a safe path off Flyway.
Root Cause Analysis
The two tools track applied migrations in incompatible ways, and that incompatibility — not the SQL — is what makes a cutover risky. Flyway records each applied script as a row in flyway_schema_history keyed by an integer-ish version (V2, V2.1) plus a checksum of the file. Liquibase records each applied changeset in DATABASECHANGELOG keyed by the triple (id, author, filename) plus an MD5 checksum. Neither tool reads the other’s table. So when Liquibase first runs, it sees an empty or absent DATABASECHANGELOG and concludes that nothing has been applied — even though the schema is fully built. Left unguarded, it will try to run every changeset from the start, and the first CREATE TABLE collides with the existing object.
The whole problem reduces to one task: tell Liquibase that everything up to a cutoff point is already applied, without executing it. Liquibase provides exactly two primitives for that — changelog-sync, which marks changesets as run without executing them, and changeset attributes that let you import existing structure. The migration is therefore not a data operation at all; it is a bookkeeping reconciliation between two history tables.
| Concern | Flyway | Liquibase |
|---|---|---|
| History table | flyway_schema_history |
DATABASECHANGELOG |
| Migration identity | version number + checksum | id + author + file + checksum |
| “Mark applied without running” | flyway baseline |
liquibase changelog-sync |
| Adopt an existing schema | baselineVersion |
changelog-sync + generated changelog |
| Rollback support | Pro / undo scripts | native rollback blocks per changeset |
Because the operation is pure bookkeeping, it carries no table-rewrite lock and can run during normal traffic — the same lock-safety reasoning detailed in the Transactional vs Non-Transactional Databases guidance, applied here to metadata tables only.
Immediate Mitigation
If a teammate has already pointed Liquibase at the database and it is now failing or has created a stray DATABASECHANGELOG, stabilize before proceeding.
-
Stop running Liquibase
updateagainst production. Any furtherupdatemay attempt to apply already-live changesets. Disable the Liquibase step in the pipeline temporarily. -
Capture the current schema as a Liquibase changelog using generate-changelog against the live database. This produces the import that represents everything Flyway has applied.
# Shell · run with a READ-ONLY DB role · generates a changelog, applies nothing
# Safe: introspection only; produces baseline-changelog.xml describing the live schema.
liquibase --url="$PROD_READONLY_URL" \
--changelog-file=baseline-changelog.xml \
generate-changelog
- Mark that baseline as already applied with
changelog-sync, which writesDATABASECHANGELOGrows without executing any DDL. This is the key step that prevents the re-apply collision.
# Shell · run with a migration role that can write DATABASECHANGELOG only · runs no DDL
# changelog-sync records every changeset as applied; the live schema is untouched.
liquibase --url="$PROD_URL" \
--changelog-file=baseline-changelog.xml \
changelog-sync
- Verify both history tables agree before unblocking the pipeline.
DATABASECHANGELOGshould now contain a row per baseline changeset, and the live schema should be unchanged.
-- PostgreSQL · read-only · confirms Liquibase now believes the baseline is applied
SELECT count(*) AS synced_changesets FROM databasechangelog;
SELECT max(version) AS last_flyway_version FROM flyway_schema_history WHERE success;
Permanent Fix / Long-Term Pattern
Treat the move as a planned, reversible sequence with a dual-tracking overlap rather than a single cutover. The overlap is what removes the need for a window, and it mirrors the parallel-running discipline you would use for any tool migration tracked in version control — the same branch hygiene described in Git Branching Strategies for Schema Version Control.
Phase 1 — freeze Flyway at a known version. Stop writing new Flyway scripts. Record the highest applied version from flyway_schema_history; this is the cutoff line. Everything at or below it becomes the Liquibase baseline.
Phase 2 — baseline and dual-track. Run the generate-changelog and changelog-sync steps above so DATABASECHANGELOG reflects the frozen schema. Now author all new changes as Liquibase changesets only. During this window both history tables exist; Flyway is frozen but not removed, so you can fall back to it if a new Liquibase changeset misbehaves. Run the next one or two real changes through Liquibase here, in staging first, to prove the new path.
<!-- Liquibase changelog · new changes only · applied by liquibase update in CI -->
<!-- Context: runs as migration role; additive DDL only during the dual-track window. -->
<changeSet id="2026-06-21-add-region" author="platform">
<addColumn tableName="orders">
<column name="region_code" type="varchar(8)"/>
</addColumn>
<rollback>
<dropColumn tableName="orders" columnName="region_code"/>
</rollback>
</changeSet>
Phase 3 — retire Flyway. Once Liquibase has successfully owned several real deploys and both tables have stayed consistent, remove the Flyway step from the pipeline. Keep flyway_schema_history as a read-only historical record for one release cycle, then drop it. The deeper rationale for which tool to standardize on, and when the changeset model pays off, lives in the Migration Tool Comparison guidance.
Verification Checklist
versionis recorded as the documented baseline cutoff before any Liquibase command runs.liquibase generate-changelogwas run with a read-only role and the output reviewed before syncing.liquibase changelog-syncpopulatedDATABASECHANGELOGand a schema diff confirms the live schema was not altered.flyway_schema_historyandDATABASECHANGELOGagree on what is applied at the cutoff point.flyway_schema_historyis retained read-only for one release cycle after Flyway’s step is removed from the pipeline.
Frequently Asked Questions
Do I have to convert all my old Flyway SQL scripts into Liquibase changesets?
No, and you should not try. The old scripts are already applied to production, so re-expressing them as changesets adds risk with no benefit. Use generate-changelog to capture the current state as a single baseline and changelog-sync to mark it applied. Only new changes need to be authored as Liquibase changesets.
Can I run Flyway and Liquibase at the same time permanently? You can during the dual-tracking window, but not indefinitely. Two tools writing schema means two history tables that can drift, and there is no automatic reconciliation between them. The overlap exists only to de-risk the cutover; freeze Flyway to additive-zero changes during it and retire it once Liquibase owns the next few deploys.
Is there any downtime in this procedure?
No. Every operation touches only metadata: generate-changelog reads the schema, and changelog-sync writes rows to DATABASECHANGELOG without running DDL. No table is rewritten and no ACCESS EXCLUSIVE lock is held, so the procedure runs against live traffic.