Skip to content

Flyway for Java — Complete Guide

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Flyway for Java. We cover key concepts, practical examples, and best practices to help you master this topic.

Learn Flyway for Java database migrations: install and configure Flyway, write SQL-based migrations, integrate with Spring Boot, use Java-based migrations, and automate Flyway in CI/CD pipelines.

What You Learn

You will learn how to use Flyway for database migrations in Java applications: install and configure Flyway, write versioned and repeatable SQL migrations, integrate with Spring Boot, use Java-based migrations for complex logic, and automate migrations in CI/CD.

Why It Matters

Flyway is the most popular migration tool for the Java ecosystem. It supports SQL-based migrations, integrates deeply with Spring Boot, and works with any JDBC-compatible database. Understanding Flyway is essential for Java developers.

Real-World Use

DodaTech's Java-based analytics service uses Flyway for PostgreSQL migrations. 150+ SQL migrations manage the analytics schema. Spring Boot auto-configuration runs migrations on startup. Repeatable migrations manage views and functions.

Installation

<!-- Maven dependency -->
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
    <version>10.0.0</version>
</dependency>

<!-- Database driver (example: PostgreSQL) -->
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.0</version>
</dependency>
// Gradle dependency
implementation 'org.flywaydb:flyway-core:10.0.0'
implementation 'org.postgresql:postgresql:42.7.0'

Expected output: Flyway is added as a project dependency along with the database driver. Flyway supports Maven and Gradle. The driver version should match your database.

Configuration

# application.properties - Spring Boot auto-configuration
spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migration
spring.flyway.baseline-on-migrate=true
spring.flyway.clean-disabled=true

# Flyway will automatically run migrations on startup
# when spring.flyway.enabled=true (default)
# application.yml
spring:
  flyway:
    enabled: true
    locations: classpath:db/migration
    baseline-on-migrate: true
    clean-disabled: true
    out-of-order: false
    validate-on-migrate: true
// Programmatic configuration
@Configuration
public class FlywayConfig {
    @Bean
    public Flyway flyway(DataSource dataSource) {
        return Flyway.configure()
            .dataSource(dataSource)
            .locations("classpath:db/migration")
            .baselineOnMigrate(true)
            .load();
    }
}

Expected output: Spring Boot auto-configures Flyway when it detects the dependency. Migrations run automatically on application startup. Configuration controls location, baseline behavior, and validation.

SQL Migration Files

-- V1__create_users_table.sql
-- Version: V1, Description: create_users_table
-- Flyway naming: V{version}__{description}.sql

CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_users_email ON users (email);

-- V2__add_phone_to_users.sql
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
CREATE INDEX idx_users_phone ON users (phone);

-- V3__create_orders_table.sql
CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT NOT NULL REFERENCES users(id),
    total DECIMAL(10, 2) NOT NULL,
    status VARCHAR(20) DEFAULT 'pending',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_orders_user ON orders (user_id);
CREATE INDEX idx_orders_status ON orders (status);

Expected output: SQL migration files follow Flyway naming convention: V{version}__{description}.sql. Versions are numeric and ordered. Each file contains the SQL for a single migration.

Repeatable Migrations

-- R__user_full_name_view.sql
-- Repeatable migration (R prefix)
-- Re-applied when content changes

CREATE OR REPLACE VIEW user_summary AS
SELECT
    u.id,
    u.name,
    u.email,
    COUNT(o.id) AS order_count,
    COALESCE(SUM(o.total), 0) AS total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name, u.email;

-- R__update_timestamp_function.sql
CREATE OR REPLACE FUNCTION update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = CURRENT_TIMESTAMP;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Expected output: Repeatable migrations (R prefix) are re-applied on every migration run if their checksum changes. They are ideal for views, functions, and stored procedures that evolve over time.

Java-Based Migrations

// V4__BackfillUserData.java
// Java-based migration for complex logic

package db.migration;

import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import org.springframework.jdbc.core.JdbcTemplate;

public class V4__BackfillUserData extends BaseJavaMigration {

    @Override
    public void migrate(Context context) {
        JdbcTemplate jdbc = new JdbcTemplate(
            new SingleConnectionDataSource(context.getConnection(), true)
        );

        // Backfill full_name from first_name and last_name
        jdbc.update("""
            UPDATE users
            SET full_name = TRIM(
                COALESCE(first_name, '') || ' ' || COALESCE(last_name, '')
            )
            WHERE full_name IS NULL
        """);

        // Process users in batches
        int batchSize = 1000;
        int total = jdbc.queryForObject("SELECT COUNT(*) FROM users WHERE full_name IS NULL", Integer.class);

        for (int offset = 0; offset < total; offset += batchSize) {
            jdbc.update("""
                UPDATE users
                SET full_name = TRIM(
                    COALESCE(first_name, '') || ' ' || COALESCE(last_name, '')
                )
                WHERE id IN (
                    SELECT id FROM users
                    WHERE full_name IS NULL
                    ORDER BY id
                    LIMIT ?
                    OFFSET ?
                )
            """, batchSize, offset);
        }
    }
}

Expected output: Java-based migrations handle complex logic that SQL alone cannot: batch processing, API calls, file processing, or conditional logic. They implement BaseJavaMigration and override the migrate() method.

Undo Migrations

-- U1__add_phone_to_users.sql
-- Undo migration for V1

ALTER TABLE users DROP COLUMN IF EXISTS phone;
DROP INDEX IF EXISTS idx_users_phone;

-- Flyway Pro/Enterprise supports undo.
-- Open-source version requires manual rollback.
# Flyway undo command (Pro/Enterprise)
flyway undo

# For open-source: create a new migration that reverses the change
# V5__remove_phone_from_users.sql
ALTER TABLE users DROP COLUMN phone;

Expected output: Flyway Pro supports automatic undo migrations (U prefix). The open-source version requires creating a new migration to reverse previous changes. This is the recommended approach for most teams.

CI/CD Integration

# Dockerfile
FROM openjdk:17-jdk-slim
COPY target/app.jar app.jar
COPY src/main/resources/db/migration /app/db/migration
CMD ["java", "-jar", "app.jar"]
# Flyway runs on startup via Spring Boot

# GitHub Actions workflow
jobs:
  migrate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Flyway migrations
        run: |
          mvn flyway:migrate \
            -Dflyway.url=${{ secrets.DB_URL }} \
            -Dflyway.user=${{ secrets.DB_USER }} \
            -Dflyway.password=${{ secrets.DB_PASSWORD }}

  deploy:
    needs: migrate
    runs-on: ubuntu-latest
    steps:
      - name: Deploy application
        run: echo "Deploying..."

Expected output: Flyway runs as a CI/CD step before deployment. If migrations fail, the deployment is aborted. The database is always at the correct schema version when the application starts.

Common Mistakes

1. Editing Applied Migrations

Editing a Flyway migration after it has been applied changes its checksum. Flyway detects the checksum mismatch and refuses to run. Create a new migration for corrections instead.

2. Not Using Baseline for Existing Database

Running Flyway against an existing database without baseline causes errors. Use flyway baseline to mark the current state as the starting point. Flyway skips existing tables and applies only new migrations.

3. Forgetting Database Driver Dependency

Flyway needs the JDBC driver for your database. Missing driver causes ClassNotFoundException at migration time. Add the driver as a dependency alongside Flyway.

4. Long-Running Migrations Blocking Startup

Flyway runs synchronously on application startup. Long migrations delay application availability. Consider running migrations as a separate CI step instead of on application startup for large migrations.

5. Clean Command in Production

flyway clean drops all database objects. In production, this is catastrophic. Set spring.flyway.clean-disabled=true (default in Spring Boot) to prevent accidental clean operations.

Practice Questions

1. What is the Flyway migration file naming convention?

V{version}{description}.sql for versioned migrations. R{description}.sql for repeatable migrations. U{version}__{description}.sql for undo migrations (Pro). Version numbers are numeric and ordered.

2. How does Flyway track applied migrations?

Flyway creates a flyway_schema_history table in the database. It records each migration's version, description, checksum, and application timestamp. On each run, it checks this table to determine which migrations have been applied.

3. When should you use Java-based migrations instead of SQL?

Use Java-based migrations for: batch processing large datasets, calling external APIs during migration, conditional migration logic, complex data transformations that are easier in Java, and generating dynamic SQL.

4. How do you handle rollback in Flyway open-source?

Create a new migration that reverses the schema change. For example, if V2 added a column, V3 removes it. This keeps the migration history linear and reversible through forward migrations.

Challenge

Set up Flyway for a Spring Boot application with: SQL migrations for initial schema (users, orders, products), repeatable migration for a view, Java-based migration for data backfill, Spring Boot auto-configuration, GitHub Actions CI step that runs migrations before deployment, and a rollback migration Strategy.

FAQ

Does Flyway work with multiple databases?

Yes. Flyway supports PostgreSQL, MySQL, MariaDB, Oracle, SQL Server, SQLite, H2, and more. SQL syntax may differ between databases. Use database-specific SQL in migration files.

Can I use Flyway with NoSQL databases?

No. Flyway is designed for relational databases that support DDL transactions. For NoSQL databases, use database-specific migration tools or custom scripts.

How does Flyway handle migration failures?

Flyway wraps each migration in a transaction (if the database supports DDL transactions). If a migration fails, the transaction rolls back. Flyway does not mark the migration as applied.

What is the difference between Flyway and Liquibase?

Flyway uses SQL files and is simpler. Liquibase uses XML/YAML/JSON and is more flexible (supports database-agnostic changelogs, rollback, and context). Choose Flyway for simplicity, Liquibase for complex scenarios.

Can I run Flyway migrations outside of Spring Boot?

Yes. Flyway has a CLI, Maven plugin, Gradle plugin, and Java API. You can run migrations without Spring Boot. The CLI is useful for CI/CD pipelines.

Mini Project: Flyway Migration Setup

Set up Flyway for a Spring Boot application with: Maven project with Flyway dependency and PostgreSQL driver, SQL migrations (V1 users, V2 orders, V3 products), repeatable migration for view, Java migration for data backfill, Spring Boot auto-configuration with application.yml, Docker Compose with PostgreSQL and application, and CI/CD pipeline with migration step.

What's Next

Now that you understand Flyway, explore Knex for Node.js for migrations in JavaScript applications.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro