Overview
What is Kotlin Flyway By Example?
Kotlin Flyway By Example is a code-first tutorial series teaching experienced Kotlin developers how to manage database schema migrations reliably using Flyway. Through 80 heavily annotated, self-contained examples, you will achieve 95% coverage of Flyway patterns—from versioned SQL migrations and naming conventions to Gradle plugin configuration, repeatable migrations, and full Ktor integration.
This tutorial assumes you are an experienced developer familiar with Kotlin, Gradle, SQL, and relational databases. If you are new to SQL migrations in general, review SQL fundamentals before proceeding.
Why By Example?
Philosophy: Show the migration file or Kotlin configuration first, execute it second, understand through direct interaction.
Traditional tutorials explain concepts then show code. By-example tutorials reverse this: every example is a working, runnable SQL migration or Kotlin snippet with inline annotations showing exactly what happens at each step—what SQL Flyway executes, what the flyway_schema_history table records, what errors occur and why, and how to avoid common pitfalls.
Target Audience: Experienced developers who:
- Already know Kotlin and Gradle fundamentals
- Understand relational databases and SQL DDL
- Prefer learning through working code rather than narrative explanations
- Want comprehensive reference material covering 95% of production migration patterns
Not For: Developers new to databases or SQL. This tutorial moves quickly and assumes foundational knowledge.
What Does 95% Coverage Mean?
95% coverage means depth and breadth of Flyway features needed for production work, not toy examples.
Included in 95% Coverage
- Versioned Migrations: V-numbered SQL files, naming convention, version ordering
- Repeatable Migrations: R-prefixed files, checksum-based re-execution
- Naming Conventions: Version, separator, description, suffix rules
- Core API:
Flyway.configure(),dataSource(),load(),migrate(),validate(),info(),clean(),baseline() - Schema History:
flyway_schema_historytable, checksum tracking, execution state - SQL DDL Patterns: Tables, columns, indexes, foreign keys, constraints, enums, UUIDs
- Data Migrations: Seed data, reference data insertion within SQL migrations
- Schema Evolution: Adding columns, dropping columns safely, renaming, type changes
- Guard Patterns:
IF NOT EXISTS,IF EXISTS, safe drops - Build Tool Integration: Gradle plugin configuration, Maven plugin configuration
- Framework Integration: Ktor integration pattern, Spring Boot auto-configuration
- Flyway Configuration: Locations, schemas, baseline, encoding, placeholders
- Error Handling: Checksum mismatch, migration failures, out-of-order detection
- Advanced Patterns: Multi-statement migrations, transaction control, large data sets
Excluded from 95% (the remaining 5%)
- Flyway Teams/Enterprise: Commercial features (undo migrations, dry runs, drift detection)
- Exotic Databases: Oracle-specific, SQL Server-specific edge cases
- Java Migrations: Flyway Java-based migration files (
JavaMigrationinterface) - Script Migrations: Python/shell-based migration scripts
- Advanced Clustering: Multi-node deployment coordination edge cases
Tutorial Structure
80 Examples Across Three Levels
Sequential numbering: Examples 1-80 (unified reference system)
Distribution:
- Beginner (Examples 1-30): 0-40% coverage - Naming conventions, core API, basic DDL patterns, schema history, common SQL constructs
- Intermediate (Examples 31-55): 40-75% coverage - Schema evolution, repeatable migrations, error handling, build tool plugins, configuration options
- Advanced (Examples 56-80): 75-95% coverage - Complex DDL patterns, data migrations, performance, framework integration, production hardening
Five-Part Example Format
Every example follows a mandatory five-part structure:
Part 1: Brief Explanation (2-3 sentences)
Answers:
- What is this concept/pattern?
- Why does it matter in production migrations?
- When should you use it?
Example:
Example 5: Creating Tables
Versioned migrations create database tables through standard SQL DDL executed exactly once by Flyway in version order. The
CREATE TABLEstatement defines column names, types, constraints, and defaults that become permanent schema artifacts tracked inflyway_schema_history.
Part 2: Mermaid Diagram (when appropriate)
Included when (~35% of examples):
- Flyway execution flow is non-obvious
- Migration ordering or dependency relationships need visualization
- Schema relationships involve multiple tables
- Error handling or branching behavior requires illustration
Skipped when:
- Simple single-file SQL examples with clear linear flow
- Trivial configuration options
- Self-explanatory DDL statements
Diagram requirements:
- Use color-blind friendly palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC, Brown #CA9161
- Vertical orientation (mobile-first)
- Clear labels on all nodes and edges
- Comment syntax:
%%(NOT%%{ }%%)
Part 3: Heavily Annotated Code
Core requirement: Every significant line must have an inline comment
Comment annotations use -- => for SQL and // => for Kotlin:
-- V1__create_users.sql
CREATE TABLE users ( -- => DDL statement: creates "users" table in database
id UUID NOT NULL, -- => UUID column: primary key (non-nullable)
name VARCHAR(100) NOT NULL -- => VARCHAR column: max 100 chars, required
); -- => Flyway records this file in flyway_schema_history on successFlyway.configure() // => Creates FlywayConfiguration builder
.dataSource(url, user, pw) // => Sets JDBC connection (url, username, password)
.load() // => Builds Flyway instance, validates config
.migrate() // => Scans classpath, runs pending migrations in orderRequired annotations:
- SQL statements: Document what each DDL clause does
- Flyway API calls: Show what each method configures or triggers
- Schema history effects: Document what Flyway records on success
- Error cases: Document when errors occur and what triggers them
- Expected outputs: Show flyway_schema_history state or console output where relevant
Part 4: Key Takeaway (1-2 sentences)
Purpose: Distill the core insight to its essence
Must highlight:
- The most important pattern or concept
- When to apply this in production
- Common pitfalls to avoid
Example:
**Key Takeaway**: Version numbers must be unique integers or dotted integers; Flyway rejects duplicate versions and out-of-order migrations by default, so always increment versions monotonically.Part 5: Why It Matters (50-100 words)
Purpose: Connect the example to real production consequences
Must explain:
- What breaks in production without this pattern
- How this pattern prevents data loss, downtime, or inconsistency
- Why experienced developers must understand this deeply
Self-Containment Rules
Critical requirement: Examples must be copy-paste-runnable or clearly executable without external context.
SQL Migration Self-Containment
Rule: Each SQL migration file is completely standalone
Requirements:
- Complete SQL statements (no partial DDL)
- All referenced tables either created in this file or explicitly noted as prerequisites
- Runnable against a fresh PostgreSQL database
Kotlin Configuration Self-Containment
Rule: Each Kotlin snippet includes all necessary imports and context
Requirements:
- Full import statements shown
- All referenced variables declared in the example
- Runnable as a Kotlin main function or object method
How to Use This Tutorial
Prerequisites
Before starting, ensure you have:
- Kotlin 1.9+ or 2.x installed (via Gradle)
- PostgreSQL 14+ running locally or via Docker
- Basic Kotlin knowledge (functions, objects, coroutines)
- Basic SQL knowledge (DDL: CREATE TABLE, ALTER TABLE, DROP TABLE)
- Flyway dependency in
build.gradle.kts(org.flywaydb:flyway-coreandorg.flywaydb:flyway-database-postgresql)
Running Examples
SQL migration files go in:
src/main/resources/db/migration/
Kotlin Flyway initialization runs at application startup:
Flyway.configure()
.dataSource("jdbc:postgresql://localhost:5432/mydb", "user", "password")
.load()
.migrate()Learning Path
For Kotlin developers new to Flyway:
- Work through beginner examples (1-30) - Master naming conventions and core DDL
- Deep dive intermediate (31-55) - Handle schema evolution and build tool integration
- Reference advanced (56-80) - Learn production hardening and advanced patterns
For developers migrating from Liquibase or raw SQL scripts:
- Read the overview to understand Flyway philosophy
- Jump to intermediate examples (31-55) - See how Flyway differs from alternatives
- Reference beginner for Flyway-specific naming and configuration as needed
- Use advanced for production deployment patterns
For quick reference:
- Use example numbers as reference (e.g., "See Example 10 for schema history")
- Search for specific patterns (Ctrl+F for "uuid", "cascade", "repeatable", etc.)
- Copy-paste SQL migration files as starting points
Coverage Progression
As you progress through examples, you will achieve cumulative coverage:
- After Beginner (Example 30): 40% - Can manage basic schema with versioned migrations
- After Intermediate (Example 55): 75% - Can handle most production migration scenarios
- After Advanced (Example 80): 95% - Expert-level Flyway mastery for production systems
Code Annotation Philosophy
Every example uses educational annotations to show exactly what happens:
-- V3__add_index.sql
CREATE INDEX idx_users_email -- => Creates B-tree index on users.email
ON users (email); -- => Flyway runs this once; records in flyway_schema_history
-- => Speeds up: WHERE email = '...' queriesval flyway = Flyway.configure() // => FlywayConfiguration builder (fluent API)
.dataSource(jdbcUrl, user, password) // => Configures JDBC DataSource connection pool
.locations("classpath:db/migration") // => Scans this classpath path for migration files
.load() // => Validates config, creates Flyway instance
flyway.migrate() // => Executes all pending versioned migrations
// => Returns MigrateResult with count of applied migrationsAnnotations show:
- SQL DDL effects - what the statement creates, alters, or drops
- Flyway API semantics - what each method call configures or triggers
- Schema history records - what Flyway writes to
flyway_schema_history - Error conditions - when and why Flyway throws exceptions
- Production implications - performance, locking, rollback behavior
Quality Standards
Every example in this tutorial meets these standards:
- Self-contained: Copy-paste-runnable within its context
- Annotated: Every significant line has an inline comment
- Accurate: All SQL tested against PostgreSQL 16; all Flyway API calls verified against Flyway 11.x
- Production-relevant: Real-world patterns from actual Ktor applications
- Accessible: Color-blind friendly diagrams, clear structure
Next Steps
Ready to start? Choose your path:
- New to Flyway: Start with Beginner Examples (1-30)
Examples by Level
Beginner (Examples 1–30)
- Example 1: First Versioned Migration (V1__description.sql)
- Example 2: Flyway Naming Convention (V<version>__<description>.sql)
- Example 3: Flyway.configure().dataSource() Setup
- Example 4: flyway.migrate() Execution
- Example 5: Creating Tables
- Example 6: Adding Columns
- Example 7: Adding Indexes
- Example 8: Adding Foreign Keys
- Example 9: Adding Unique Constraints
- Example 10: flyway_schema_history Table
- Example 11: Flyway Info (Migration Status)
- Example 12: Flyway Validate
- Example 13: Flyway Clean (Reset Database)
- Example 14: Flyway Baseline
- Example 15: Repeatable Migrations (R__description.sql)
- Example 16: Multiple Statements in One Migration
- Example 17: NOT NULL Constraints with Defaults
- Example 18: UUID Primary Keys
- Example 19: Timestamp Columns with Defaults
- Example 20: Enum Types (PostgreSQL)
- Example 21: CHECK Constraints
- Example 22: Composite Indexes
- Example 23: Junction Tables (Many-to-Many)
- Example 24: Seed Data in Migrations
- Example 25: Cascade Delete Foreign Keys
- Example 26: IF NOT EXISTS Guards
- Example 27: Dropping Tables/Columns Safely
- Example 28: Gradle Plugin Configuration
- Example 29: Maven Plugin Configuration
- Example 30: Ktor Integration Pattern
Intermediate (Examples 31–60)
- Example 31: Java-Based Migration (extends BaseJavaMigration)
- Example 32: Kotlin-Based Migration
- Example 33: Callbacks (beforeMigrate, afterMigrate)
- Example 34: Placeholders in SQL Migrations
- Example 35: Multiple Schema Support
- Example 36: Baseline Migrations for Existing Databases
- Example 37: Cherry-Pick Specific Migrations
- Example 38: Out-of-Order Migrations
- Example 39: Migration Groups/Categories
- Example 40: Transaction Control per Migration
- Example 41: Error Handlers for Failed Migrations
- Example 42: Dry-Run Migrations
- Example 43: Data Migration with INSERT...SELECT
- Example 44: Seed Data Pattern
- Example 45: Foreign Key with ON UPDATE CASCADE
- Example 46: Composite Primary Keys
- Example 47: Partial Indexes
- Example 48: Full-Text Search Indexes
- Example 49: Creating Views
- Example 50: Creating Materialized Views
- Example 51: Trigger Functions
- Example 52: Stored Procedures
- Example 53: JSON/JSONB Columns
- Example 54: Array Columns (PostgreSQL)
- Example 55: GIN Index for JSONB
- Example 56: Table Partitioning
- Example 57: Generated Columns
- Example 58: Migration Testing with JUnit 5
- Example 59: Test Database with Testcontainers
- Example 60: Exposed ORM Integration Pattern
Advanced (Examples 61–85)
- Example 61: Custom MigrationResolver
- Example 62: Custom MigrationExecutor
- Example 63: Zero-Downtime Column Addition
- Example 64: Zero-Downtime Column Removal (3-Phase)
- Example 65: Zero-Downtime Table Rename
- Example 66: Large Table Migration with Batched Updates
- Example 67: Online Index Creation (CONCURRENTLY)
- Example 68: Data Backfill Pattern
- Example 69: Flyway in CI/CD Pipeline
- Example 70: Migration Rollback Testing
- Example 71: Blue-Green Deployment Migrations
- Example 72: Feature Flag Migration Pattern
- Example 73: Multi-Tenant Schema Migration
- Example 74: Migration with pgcrypto Encryption
- Example 75: Audit Trail Table Migration
- Example 76: Soft Delete Schema Pattern
- Example 77: Flyway with Exposed ORM Advanced Patterns
- Example 78: Schema Comparison/Drift Detection
- Example 79: Migration Performance Benchmarking
- Example 80: Migration Dependency Graph
- Example 81: Migration Squashing Pattern
- Example 82: Flyway API Programmatic Control
- Example 83: Custom Flyway Extensions
- Example 84: Production Migration Checklist
- Example 85: Migration Monitoring and Alerting
Last updated March 26, 2026