Schema migrations with Flyway and seed data

Versioned and repeatable migrations, baselining an existing schema, repeatable seed scripts, and a rollback strategy that works.

Versioned migrations

Set spring.jpa.hibernate.ddl-auto to validate and let Flyway own the schema. Hibernate then fails fast when the entity no longer matches the table instead of quietly altering it.

-- src/main/resources/db/migration/V1__create_book_table.sql
create table book (
  id          bigserial primary key,
  title       text not null,
  author_id   bigint not null references author(id),
  published_at timestamptz,
  created_at  timestamptz not null default now()
);

create index idx_book_author on book(author_id);

-- V2__add_isbn_to_book.sql
alter table book add column isbn text;
create unique index idx_book_isbn on book(isbn) where isbn is not null;
PrefixRunsUse for
V1__, V2__Exactly once, in orderSchema changes
R__Whenever the checksum changes, after versioned onesViews, functions, seed reference data
U1__Manually triggeredUndo scripts, when you insist on rollback files
⚠️
Never edit a migration that has already run in any shared environment: Flyway validates checksums and will refuse to start. Fix forward with a new version instead.

Configuration and baselining

spring:
  flyway:
    enabled: true
    locations: classpath:db/migration
    baseline-on-migrate: true
    baseline-version: 0
    out-of-order: false
    validate-on-migrate: true
  jpa:
    hibernate:
      ddl-auto: validate
    properties:
      hibernate:
        jdbc:
          time_zone: UTC

baseline-on-migrate is for bringing an existing database under Flyway control: it writes a baseline row so migrations above that version apply and ones below are skipped. Use it once, with care, never as a daily setting.

  • One migration per logical change, small enough to review in a diff.
  • Adding a NOT NULL column needs three steps: add nullable, backfill, then add the constraint.
  • Run migrations at deploy time as a separate step so a failure stops the release before the new code starts.

Reference data and test seeding

-- R__seed_countries.sql  (repeatable: re-runs when the file changes)
insert into country (code, name)
values ('DE', 'Germany'), ('FR', 'France'), ('GB', 'United Kingdom')
on conflict (code) do update set name = excluded.name;

Keep deterministic reference data in repeatable migrations. For per-environment demo rows, use a Spring CommandLineRunner guarded by a profile, or Testcontainers with a @Sql script - do not put demo users in a versioned migration that reaches production.

FAQ

How do I roll back a bad migration?
In practice: write a new forward migration that repairs the schema, because restoring data is rarely possible with a schema-only undo. Keep the database backup and the failing migration version in the incident notes, and rehearse the repair on a restore of production.
Flyway or Liquibase?
Flyway is plain SQL with a small learning curve and full control over the statements. Liquibase is better when you need one changelog to target several database engines or you want engine-independent rollback definitions.

Configuration, JPA data access and profiles Testing Spring Boot applications

Last refreshed 2026-09-18.