When managing database schema changes manually, it’s easy to end up with mismatched table structures between development and production, or to run SQL scripts in the wrong order. This becomes especially painful in team environments where there’s no clear record of who changed what and when.
Flyway lets you version-control your database schema changes the same way you version-control your code. This article walks through how to integrate Flyway into a Spring Boot project and manage migrations safely.
What is Flyway?
Flyway is a tool that automates version control for database schemas. It executes SQL files in order and records which migrations have been applied in a flyway_schema_history table.
Many developers use Hibernate’s ddl-auto to auto-generate tables, which is convenient early in development but carries the risk of unexpected changes in production. With Flyway, you have explicit control over schema changes and a shared history visible to the entire team.
Adding Flyway to Spring Boot
Start by adding the dependency. For Gradle:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.flywaydb:flyway-core'
runtimeOnly 'org.postgresql:postgresql'
}
For Maven:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
Next, configure your database connection and Flyway settings in application.properties:
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=user
spring.datasource.password=pass
spring.jpa.hibernate.ddl-auto=validate
Flyway is enabled by default in Spring Boot 3.x, so you don’t need to explicitly set spring.flyway.enabled=true. Only set it to false if you want to disable it.
Setting ddl-auto=validate disables Hibernate’s automatic table generation and limits it to verifying that your entities match the schema. Since Flyway and Hibernate operate independently, the recommended pattern is to manage your schema with Flyway and disable (or set to validate) Hibernate’s auto-ddl.
For more details on data source configuration, see Spring Bootのapplication.propertiesで設定を管理する基本.
Placing Migration Scripts
Flyway automatically scans the src/main/resources/db/migration directory, so place your SQL files there.
File names must follow the format V{version}__{description}.sql:
V1__init.sqlV2__add_email_column.sqlV3__create_orders_table.sql
Version numbers must be unique and in ascending order. The description is separated from the version by two underscores (__). Flyway will not recognize files that don’t follow this naming convention.
Creating the Initial Schema
Let’s create the first migration script, V1__init.sql:
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_users_username ON users(username);
When you start your Spring Boot application, Flyway will automatically execute this script. Afterwards, you should find both the users table and the flyway_schema_history table in your database:
SELECT * FROM flyway_schema_history;
This table records the version, description, execution timestamp, and checksum of each applied migration. If the checksum of a previously recorded script changes, Flyway will throw an error and refuse to proceed — protecting against history tampering and unintended changes.
Adding Schema Changes
To add a column to an existing table, create a new migration script:
-- V2__add_email_column.sql
ALTER TABLE users ADD COLUMN email VARCHAR(100);
If you want to add a NOT NULL constraint and the table already has data, you need to be careful. The safe approach is to first add the column as nullable, set a default value, and then apply the NOT NULL constraint:
-- V3__make_email_required.sql
UPDATE users SET email = '[email protected]' WHERE email IS NULL;
ALTER TABLE users ALTER COLUMN email SET NOT NULL;
This example uses a placeholder value, but in real projects you’ll need to design appropriate values and a data cleansing strategy based on your business requirements. example.com is a reserved domain per RFC 2606, so it’s safe for testing, but adjust it to your requirements in production.
As a general rule for grouping changes: related changes (such as creating a table and adding a foreign key) work well in a single script since they’re easier to roll back together. Independent changes are better split into separate scripts, making it easier to isolate problems when something goes wrong.
Applying to Existing Databases and Environment-Specific Configuration
To introduce Flyway into a database that already has tables, use baseline-on-migrate:
spring.flyway.baseline-on-migrate=true
spring.flyway.baseline-version=1
With this configuration, Flyway skips migrations up to the specified baseline-version (default is 1) for existing databases and adds a baseline record to the history table. Only migrations from V2 onwards will then be executed.
For fresh environments, all migrations (starting from V1) run as normal — so if you reproduce the existing table structure in V1__init.sql, new environments will get the same schema. In short: V1 is for setting up new environments; baseline is for retrofitting Flyway onto existing ones.
Setting baseline-version=0 treats the existing schema as the initial state and starts applying from V1. Setting baseline-version=1 treats V1 as already applied and starts from V2. Adjust based on your environment.
Production environments require extra care. Use separate configuration files per profile:
# application-prod.properties
spring.flyway.clean-disabled=true
spring.flyway.baseline-on-migrate=false
Flyway’s clean command drops all tables in the database — a dangerous operation. Set clean-disabled=true in production to prevent accidental execution. In development, keeping it false gives you the flexibility to reset the schema during testing.
For environment-specific configuration, create application-dev.properties and application-prod.properties and switch between them using profiles. See Spring BootのProfileを使って環境によって違う設定を安全に切り替える方法 for details.
Handling Migration Failures
When an error occurs during migration, a record with success=false is left in flyway_schema_history:
SELECT version, description, success FROM flyway_schema_history WHERE success = false;
Checksum mismatch errors occur when you modify an already-applied script. In production, the rule is never to modify existing scripts — add changes as a new version instead.
If you need to fix a script that was accidentally committed during development, follow these steps:
- Fix the script
- Run
./gradlew flywayRepair(ormvn flyway:repair) to recalculate checksums and remove failed records - Restart the application to re-run the migration
The same process applies when a migration fails due to a SQL syntax error or constraint violation: fix the script, run flywayRepair, and restart.
In development environments you can also manually delete failed records from the history table, but directly manipulating the history table in production is dangerous due to audit trail and consistency risks. Always use the flywayRepair command instead.
Team Development and Rollback Strategy
In team environments, it’s easy for two developers to create migrations with the same version number. Sequential numbering (V1, V2…) is simple but prone to conflicts across branches. Timestamp-based naming (e.g., V20260204120000__...) avoids conflicts automatically but reduces readability. Choose based on your team’s workflow.
V20260204120000__add_user_email.sql
V20260204130000__create_orders_table.sql
If you discover a version number conflict during a Git merge, rename the file that was created later and increment its version number. Treat migration scripts the same as code — review them for impact on existing data and have a rollback plan ready.
The free version of Flyway does not include automatic rollback. The Pro/Teams edition supports undo scripts in the U1__, U2__ format executed via flyway undo, but with the free version you’ll need to write and manually run your own rollback SQL scripts.
The most reliable safety net is to take a database backup before any production migration and restore from it if something goes wrong. Thoroughly testing in a staging environment before applying to production dramatically reduces the risk.
Before deploying to production, confirm the following:
- A database backup has been taken
- The migration ran successfully in a staging environment
- A rollback plan is in place
- A maintenance window has been scheduled
After applying migrations, check flyway_schema_history to confirm the latest migration shows success=true and verify that the application starts normally.
For Docker-based deployments, see Spring BootアプリケーションをDockerでコンテナ化する実践ガイド.
Summary
Flyway gives you a clear audit trail of schema changes and keeps your environments consistent. It eliminates human error from manually running SQL scripts and improves efficiency in team development.
By following versioning rules and thoroughly validating before production deployments, you can significantly reduce deployment risk. Start by trying it in a development environment and find the workflow that fits your team best.
For JPA entity design, see Spring BootのJPAでエンティティのリレーションシップをマッピングする方法.