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 justflyway-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:
- Creates the
flyway_schema_historytable if absent. - Reads applied versions from that table.
- Applies any pending
V*scripts in version order. - 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;
| Prefix | Type | Runs |
|---|---|---|
V | Versioned | Once, in version order |
R | Repeatable | Whenever checksum changes, after versioned |
U | Undo (paid) | To roll back a versioned migration |
Best Practices
- Keep
ddl-auto: validate(ornone) 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.