Skip to content
Spring Boot sb databases 3 min read

Flyway Migrations

Flyway brings version control to your database schema. Instead of letting Hibernate guess at schema changes with ddl-auto, you write ordered SQL migration scripts that Flyway applies in sequence and records. Spring Boot detects Flyway on the classpath and runs pending migrations automatically on startup, before JPA initializes.

Why migrations

Relying on ddl-auto: update in production is risky: it can silently add columns, never removes anything, and gives you no audit trail. Migrations make every schema change an explicit, reviewable, repeatable artifact that runs identically across dev, staging, and production. Flyway tracks which scripts have run so each migration applies exactly once.

Adding the dependency

Add flyway-core. For databases that need a dedicated module (MySQL, PostgreSQL on newer versions), include the matching artifact too.

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>

<!-- Required for MySQL/MariaDB -->
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-mysql</artifactId>
</dependency>

Note: Spring Boot manages the Flyway version through spring-boot-starter-parent, so omit explicit <version> tags. PostgreSQL works with just flyway-core.

Versioned migrations

Place SQL files under src/main/resources/db/migration. Spring Boot scans this location by default. Versioned migrations follow a strict naming pattern:

V<VERSION>__<description>.sql
src/main/resources/db/migration/
├── V1__create_products.sql
├── V2__add_price_index.sql
└── V3__add_category_column.sql

The format is V, a version number, two underscores, a description, and .sql. A first migration:

-- V1__create_products.sql
CREATE TABLE products (
    id    BIGSERIAL PRIMARY KEY,
    name  VARCHAR(120) NOT NULL,
    price NUMERIC(10, 2)
);
-- V2__add_price_index.sql
CREATE INDEX idx_products_price ON products (price);

Warning: Once a versioned migration has run anywhere, never edit it. Flyway stores a checksum and will refuse to start if a previously applied script changed. To fix a mistake, write a new migration.

Auto-run on startup

With Flyway on the classpath and a configured datasource, no extra code is needed. On startup Spring Boot runs Flyway, which:

  1. Creates the flyway_schema_history table if absent.
  2. Reads applied versions from that table.
  3. Applies any pending V* scripts in version order.
  4. Initializes JPA afterward, so entities meet the migrated schema.
spring:
  flyway:
    enabled: true              # default when flyway-core is present
    locations: classpath:db/migration
    baseline-on-migrate: false
  jpa:
    hibernate:
      ddl-auto: validate       # let Flyway own the schema

Set ddl-auto: validate so Hibernate verifies, but never modifies, the Flyway-managed schema.

The flyway_schema_history table

Flyway records every applied migration in flyway_schema_history. Inspecting it tells you exactly what ran.

| installed_rank | version | description       | type | checksum   | success |
|----------------|---------|-------------------|------|------------|---------|
| 1              | 1       | create products   | SQL  | 982734019  | t       |
| 2              | 2       | add price index   | SQL  | 119283746  | t       |
| 3              | 3       | add category col  | SQL  | 552310987  | t       |

The checksum is what Flyway compares to detect edits to already-applied scripts.

Baselining an existing database

If you adopt Flyway on a database that already has tables, baseline it so Flyway treats the current state as a starting point and only applies migrations numbered above the baseline.

spring:
  flyway:
    baseline-on-migrate: true
    baseline-version: 1

With this, Flyway stamps the existing schema at version 1 and applies V2, V3, and beyond.

Repeatable migrations

Scripts prefixed with R__ instead of V are repeatable: Flyway re-runs them whenever their checksum changes, after all versioned migrations. They are ideal for views, stored procedures, and reference data you redefine idempotently.

-- R__active_products_view.sql
CREATE OR REPLACE VIEW active_products AS
SELECT id, name, price FROM products WHERE price IS NOT NULL;
PrefixTypeRuns
VVersionedOnce, in version order
RRepeatableWhenever checksum changes, after versioned
UUndo (paid)To roll back a versioned migration

Best Practices

  • Keep ddl-auto: validate (or none) once Flyway owns the schema.
  • Treat applied migrations as immutable; fix forward with new scripts.
  • Name scripts clearly and commit them with the code change that needs them.
  • Use repeatable (R__) migrations for views and procedures.
  • Test migrations against a disposable database (for example, Testcontainers) in CI before they reach production.
Last updated June 13, 2026
Was this helpful?