Flyway vs Liquibase: Which Database Migration Tool Should You Use?

Flyway and Liquibase solve the same problem, versioning a database schema alongside the code that uses it, with two different philosophies. Flyway is a thin runner for ordered SQL scripts. Liquibase is a changelog engine with an abstraction layer, rollback support and richer control over what runs where. Pick Flyway when your team writes SQL for one engine and wants the smallest possible tool; pick Liquibase when you need portable change definitions, per-change rollback or fine-grained execution rules.

The two models in one minute#

Flyway looks at a directory of migration files, sorts them by version, applies the ones the database has not seen yet and records each in a table called flyway_schema_history, together with a checksum of the file. A migration is a plain SQL script, or a Java class when you need logic. There is no rollback concept in the open-source edition: you fix forward by writing another migration.

db/migration/
  V1__create_owners_and_pets.sql
  V2__create_vets.sql
  V3__create_visits.sql
  R__owner_summary_view.sql

Liquibase reads a changelog, a file (or tree of files) made of changesets. Each changeset is identified by an id, an author and the path of the changelog it lives in, and is recorded in DATABASECHANGELOG once applied. A second table, DATABASECHANGELOGLOCK, stops two processes from migrating at the same time. Changesets can be written in XML, YAML, JSON or a lightly annotated SQL dialect.

<changeSet id="002-vets" author="schemadrawer">
    <createTable tableName="vets">
        <column name="id" type="INT">
            <constraints primaryKey="true" primaryKeyName="pk_vets" nullable="false"/>
        </column>
        <column name="first_name" type="VARCHAR(50)">
            <constraints nullable="false"/>
        </column>
        <column name="last_name" type="VARCHAR(50)">
            <constraints nullable="false"/>
        </column>
    </createTable>
</changeSet>

The difference is already visible. Flyway trusts you to write correct SQL for your engine. Liquibase describes the intent, create this table with these columns, and generates the DDL for whichever database it is pointed at.

Formats and readability#

With Flyway, the migration is the SQL that will run. Anyone who can read SQL can review a pull request, and the file works in psql or any other client without translation. The price is that your migrations are tied to one database dialect, which is rarely a problem for a product with one production database but matters for a library or a product shipped to customers who choose their own engine.

Liquibase gives you a choice. Its abstract change types (createTable, addForeignKeyConstraint, addColumn and dozens more) are portable and come with automatic rollback for most of them. Its formatted SQL changelog lets you keep raw SQL while still getting changeset tracking:

--liquibase formatted sql

--changeset schemadrawer:001-owners
CREATE TABLE owners
(
    id         INT          NOT NULL,
    first_name VARCHAR(50)  NOT NULL,
    last_name  VARCHAR(50)  NOT NULL,
    CONSTRAINT pk_owners PRIMARY KEY (id)
);
--rollback DROP TABLE owners;

Teams that choose XML or YAML usually do it for the tooling: schema validation in the IDE, a diff that shows a column being added rather than a block of DDL moving around, and the ability to target several engines from one file. Teams that choose formatted SQL want Liquibase's tracking and rollback with the readability of plain scripts. The changelog format comparison goes deeper into that choice.

Rollback and undo#

This is the sharpest difference. Liquibase treats rollback as a first-class operation: rollbackCount, rollbackToDate and rollback to a named tag are all available, and for the abstract change types Liquibase knows the inverse (a createTable rolls back with a drop). For raw SQL you write the inverse yourself in a rollback block; if you leave it out, the rollback of that changeset fails, which is at least explicit.

Flyway's open-source edition moves in one direction. Undo migrations (the U prefix) exist, but they are part of the paid editions. In practice many Flyway teams never miss them: rolling a schema back on a production database with live data is dangerous whatever the tool, and the usual answer is to ship a new forward migration. If your compliance process or deployment pipeline requires a scripted rollback for every change, that is a real point for Liquibase.

Checksums, validation and drift#

Both tools fingerprint what they apply. Flyway stores a checksum per script and, by default, validates on every migrate: a changed file that has already been applied stops the run with an error. flyway repair realigns the checksums when a change was deliberate (a comment fix, say) and removes the record of a failed migration. Flyway's repeatable migrations (R__) invert the rule: they rerun whenever their checksum changes, which is exactly what you want for views and stored procedures.

Liquibase does the same per changeset, and offers runOnChange for the repeatable case and runAlways for the rare changeset that should execute on every run. A changed checksum fails validation unless the changeset lists it in validCheckSum, or you clear the stored checksums. Both tools therefore catch the classic mistake, editing a migration that already ran; neither catches drift caused by hand-made changes to the database, which is a job for schema comparison and is covered in schema drift: detection and prevention.

Integrations and ecosystem#

Both are first-class citizens in the JVM world. Spring Boot auto-configures either one when it is on the classpath (spring.flyway.* and spring.liquibase.* properties), runs the migrations at startup and exposes them through the Java API for tests. Both have Maven and Gradle plugins, command-line distributions and official Docker images, so a CI job can migrate a throw-away database without a JVM project around it.

Outside Java the picture is similar: both CLIs are self-contained and language-agnostic, and both are common in Node, Go and Python shops that simply want SQL migrations with a history table. Flyway's Java migrations and Liquibase's custom change classes exist for logic that SQL cannot express, but both are best kept rare.

Side by side#

AspectFlywayLiquibase
Unit of changeVersioned file (V), repeatable file (R)Changeset (id, author, file)
FormatsSQL, JavaXML, YAML, JSON, formatted SQL
Portability across enginesYou write per-dialect SQLAbstract change types generate DDL per engine
RollbackFix forward; undo scripts in paid editionsBuilt in; automatic for many change types
Conditional executionPlaceholders, per-location configurationContexts, labels, preconditions, dbms filters
History tablesflyway_schema_historyDATABASECHANGELOG, DATABASECHANGELOGLOCK
Learning curveAn afternoonA few days to use well

Team workflow#

The day-to-day friction is different. Flyway's numeric versions collide when two branches both add V12; teams solve it with timestamp versions or by enabling outOfOrder, and the naming rules are worth knowing before the first collision. Liquibase changesets are identified by author and id, so two branches rarely clash on identity, but they can still clash on order when both touch the same table, and a large XML changelog produces noisier diffs than a directory of small SQL files.

Reviewability favours Flyway slightly: a reviewer reads the exact statement that will run. Liquibase reviewers read intent and trust the generator, which is fine until an engine-specific detail matters (an index type, a collation, a partial index), at which point you drop to a sql change anyway.

Decision guide#

  • One production engine, a team fluent in SQL, a preference for small tools: Flyway.
  • A product deployed on customers' databases of different vendors: Liquibase with abstract change types.
  • A hard requirement for scripted rollback of every change: Liquibase.
  • Heavy use of views, functions and procedures: either; Flyway's repeatable migrations and Liquibase's runOnChange both cover it.
  • Many teams and services sharing conventions: whichever you already run somewhere; consistency beats features.

Whatever you choose, the migration files become the most accurate description of your schema that exists, more accurate than the ORM model and far more accurate than the wiki. Treat them that way: review them, keep them small, never edit one that has been applied.

Where SchemaDrawer fits#

Because the migrations are the source of truth, they are also the right input for a diagram. SchemaDrawer takes a directory of Flyway SQL scripts or a Liquibase changelog (XML, YAML, JSON or formatted SQL), really runs it with the tool itself against a throw-away PostgreSQL, MySQL, MariaDB, SQL Server, Oracle or SQLite of the version you choose, reads the resulting tables, columns, primary keys and foreign keys over JDBC and returns an SVG entity-relationship diagram. There is nothing to parse and nothing to keep in sync: the picture is what the migrations produce. See the Flyway and Liquibase pages for the exact upload layout, or the getting started guide for the two HTTP calls involved.

Key takeaways#

  • Flyway runs ordered SQL scripts; Liquibase runs changesets that describe intent.
  • Liquibase has built-in rollback; open-source Flyway is forward-only.
  • Both checksum applied changes and fail on edits; neither detects manual drift.
  • Spring Boot, Maven, Gradle, CLI and Docker support are equivalent.
  • Choose by team habits and portability needs, not by feature lists.
Marek Hudyma
Builds SchemaDrawer, a service that draws database diagrams by running your migrations on a real engine. About

Draw your schema

Upload your migration files, pick the engine version you deploy to, get an SVG back. Free while in early access.

Get started
Keep reading

Related articles

Draw your schema in the next five minutes

One HTTP call with your migration files, one poll, one SVG. Free while we build it.