Flyway for Java — Complete Guide
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
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