Database Migrations with Flyway

Module 7 · ~8 min read
Flyway treats database schema changes as versioned code. Each migration is a SQL file with a version number. Flyway tracks which migrations have been applied in a flyway_schema_history table and runs only the new ones on startup. Schema evolution becomes reproducible and auditable.

Why Flyway?

Naming Convention

Flyway migration files follow the pattern V{version}__{description}.sql (two underscores):

Migrations run in version order. Gaps are allowed (V1, V3, V5 is valid).

Never modify an existing migration file after it has been applied to any environment. Flyway stores the checksum of each migration. Modifying a file will cause a startup failure on every environment that already ran it. To fix a mistake, create a new migration.

Example: V1 Initial Schema

V1__init_schema.sql — users table View source ↗
CREATE TABLE users (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username    VARCHAR(50) UNIQUE NOT NULL,
    password    VARCHAR(255) NOT NULL,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

Idempotent Patterns

For migrations that might need to be re-run (e.g., data backfills), use idempotent SQL patterns:

Idempotent migration patterns
CREATE TABLE IF NOT EXISTS my_table (...);

INSERT INTO config_values (key, value)
VALUES ('max_results', '10')
ON CONFLICT (key) DO NOTHING;

ALTER TABLE my_table
  ADD COLUMN IF NOT EXISTS new_col VARCHAR(100);

Application Configuration

application.yml — Flyway configuration View source ↗
spring:
  flyway:
    enabled: true
    locations: classpath:db/migration
    baseline-on-migrate: false

Production Rules

Never use spring.jpa.hibernate.ddl-auto: create or update in production alongside Flyway. Hibernate's schema management and Flyway will conflict, potentially dropping tables or leaving the schema in an inconsistent state. Set ddl-auto: validate or none.
ddl-auto valueEnvironmentEffect
noneProductionHibernate makes no schema changes — Flyway controls everything
validateStaging / CIHibernate checks the schema matches entities — fails fast on mismatch
createNever with FlywayDrops and recreates schema — destroys data
updateNever with FlywayConflicts with Flyway's schema ownership