100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Java

Database Migrations with Flyway

Learn how Flyway version-controls your schema with plain SQL migration scripts, integrates with Spring Boot, and keeps environments consistent.

Data LayerIntermediate9 min readJul 10, 2026
Analogies

Why Schema Migrations Matter

Relying on Hibernate's ddl-auto=update to manage a production schema is fragile: it can't express certain changes (renaming a column, splitting one column into two, backfilling data), can't be reviewed in a pull request the way code can, and offers no way to roll a change back or replay history on a fresh environment. Flyway solves this by treating schema changes as an ordered sequence of plain SQL (or Java-based) migration scripts, each one applied exactly once and tracked in a flyway_schema_history table, so the schema's current state is always the deterministic result of replaying a known, version-controlled sequence of steps.

🏏

Cricket analogy: Relying on ddl-auto=update is like a groundsman making ad hoc pitch changes on match day without any record, whereas Flyway is like following an official, dated ground-maintenance log where every intervention is recorded and can be reviewed or replayed exactly.

Naming Conventions and Migration Types

Flyway's versioned migrations follow a strict naming convention: V<version>__<description>.sql, for example V1__create_books_table.sql or V2__add_isbn_column_to_books.sql, where the version number determines application order and the double underscore separates version from description. Flyway also supports repeatable migrations, prefixed R__ instead of V, which are re-applied every time their checksum changes rather than only once — useful for scripts like view or stored-procedure definitions that you want to keep in sync with the latest version rather than layering incremental ALTER statements.

🏏

Cricket analogy: Versioned migrations (V1, V2, V3) are like numbered overs in an innings that must be bowled strictly in order, while a repeatable migration is like a fielding drill re-run before every single match, not a one-time event in the sequence.

sql
-- src/main/resources/db/migration/V1__create_books_table.sql
CREATE TABLE authors (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    first_name VARCHAR(100) NOT NULL,
    last_name  VARCHAR(100) NOT NULL
);

CREATE TABLE books (
    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    title VARCHAR(250) NOT NULL,
    isbn VARCHAR(13) UNIQUE,
    status VARCHAR(20) NOT NULL DEFAULT 'DRAFT',
    author_id BIGINT NOT NULL REFERENCES authors(id)
);

-- src/main/resources/db/migration/V2__add_published_year_to_books.sql
ALTER TABLE books ADD COLUMN published_year INTEGER;

-- src/main/resources/db/migration/R__refresh_active_books_view.sql
CREATE OR REPLACE VIEW active_books AS
SELECT b.* FROM books b WHERE b.status <> 'ARCHIVED';

Integrating Flyway with Spring Boot

Adding the flyway-core dependency is enough for Spring Boot's auto-configuration to detect it and run any pending migrations found in src/main/resources/db/migration against the configured DataSource automatically on application startup, before the JPA EntityManagerFactory is initialized. Because Flyway now owns the schema, the correct pairing is spring.jpa.hibernate.ddl-auto=validate, so Hibernate confirms your entity mappings match what Flyway has already created rather than trying to manage the schema itself — the two tools each own a clear, non-overlapping responsibility.

🏏

Cricket analogy: Flyway running before Hibernate initializes is like the ground staff preparing the pitch before play starts, and the umpires (Hibernate's ddl-auto=validate) simply inspecting it meets regulation rather than relaying it themselves.

Flyway computes and stores a checksum for every applied versioned migration in flyway_schema_history. If you edit an already-applied V-prefixed script, Flyway will detect the checksum mismatch on the next run and fail fast rather than silently reapplying a modified script — the fix is always a new migration, never an edit to history.

Baselining and Team Workflow

When introducing Flyway to a database that already has tables (a common scenario when adopting migrations mid-project), spring.flyway.baseline-on-migrate=true tells Flyway to treat the current schema state as version 0 (or a configured baseline version) rather than trying to run V1 against tables that already exist. In a team setting, the convention is that every developer creates a new, uniquely versioned migration file rather than editing someone else's in-flight migration, and CI pipelines typically run flyway validate (or rely on Spring Boot's own startup check) to catch a missing or out-of-order migration before it reaches a shared environment.

🏏

Cricket analogy: Baselining is like a new umpire joining mid-series and being told 'treat the current match state as the official starting point,' rather than trying to replay every ball from the first Test of the series.

Never run flyway repair or manually edit rows in flyway_schema_history as a routine fix for a failed migration in a shared environment without understanding exactly what happened — it marks a failed migration as resolved without actually reverting or completing the underlying schema change, which can leave the real database structure silently out of sync with what the history table claims.

  • Flyway applies ordered, version-controlled SQL migrations, unlike ddl-auto which can't express renames, backfills, or rollbacks safely.
  • Versioned migrations follow V<version>__<description>.sql naming and apply exactly once, tracked in flyway_schema_history.
  • Repeatable migrations (R__ prefix) re-apply whenever their checksum changes, ideal for views and stored procedures.
  • Flyway runs automatically on Spring Boot startup, before the JPA EntityManagerFactory initializes, when flyway-core is on the classpath.
  • Pair Flyway with spring.jpa.hibernate.ddl-auto=validate so schema ownership is clearly Flyway's, not Hibernate's.
  • spring.flyway.baseline-on-migrate=true lets Flyway adopt an already-existing schema as its starting point.
  • Never edit an already-applied versioned migration file; add a new migration instead to avoid checksum mismatches.

Practice what you learned

Was this page helpful?

Topics covered

#Java#SpringBootStudyNotes#WebDevelopment#DatabaseMigrationsWithFlyway#Database#Migrations#Flyway#Schema#SQL#StudyNotes#SkillVeris