Published , Last updated
Database schema versioning is crucial for managing changes across development, staging, and production environments. When working with Spring Boot, Flyway emerges as a robust tool for handling schema migrations. While SQL-based migrations are common, Java-based migrations provide flexibility, maintainability, and programmatic control. In this blog post, I’ll share my experience integrating Flyway into a Spring Boot application with Java-based migration and seeding, ensuring compatibility for both Maven and Gradle projects.
While Flyway supports raw SQL migrations, Java-based migrations offer:
Add the following dependency in your pom.xml
:
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
Include this in your build.gradle
:
implementation 'org.flywaydb:flyway-core'
If you're using multiple databases (MySQL/PostgreSQL), you’ll also need corresponding JDBC drivers.
application.yml
spring:
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true
validate-on-migrate: true
out-of-order: false
Or use application.properties
:
spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migration
spring.flyway.baseline-on-migrate=true
spring.flyway.validate-on-migrate=true
spring.flyway.out-of-order=false
By default, Flyway scans the db/migration
directory under src/main/resources
for SQL-based migrations. However, for Java-based migrations, the classes should be located under src/main/java
. Therefore, create a db/migration
package inside src/main/java
, and place your Java migration classes there.
src/
├── main/
│ ├── java/
│ │ └── db.migration/
│ │ ├── V1__CreateUsersTable.java
│ │ └── V2__SeedUsersTable.java
│ └── resources/
│ └── application.yml or application.properties
Here’s an example of a schema migration:
package db.migration;
import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import java.sql.Statement;
public class V1__CreateUsersTable extends BaseJavaMigration {
@Override
public void migrate(Context context) throws Exception {
try (Statement stmt = context.getConnection().createStatement()) {
stmt.execute("CREATE TABLE users (" +
"id BIGINT PRIMARY KEY AUTO_INCREMENT," +
"username VARCHAR(100) NOT NULL," +
"email VARCHAR(100))");
}
}
}
Lets create a migration file V2__SeedUsersTable.java
:
package db.migration;
import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import java.sql.PreparedStatement;
public class V2__SeedUsersTable extends BaseJavaMigration {
@Override
public void migrate(Context context) throws Exception {
String insert = "INSERT INTO users (username, email) VALUES (?, ?)";
try (PreparedStatement ps = context.getConnection().prepareStatement(insert)) {
ps.setString(1, "admin");
ps.setString(2, "[email protected]");
ps.executeUpdate();
}
}
}
Tip: You can control execution using spring.profiles.active
or spring.flyway.enabled=false
for environments where seeding isn’t needed.
Using Java migrations, you can branch based on the JDBC URL or DB metadata:
String dbProduct = context.getConnection().getMetaData().getDatabaseProductName();
if ("MySQL".equalsIgnoreCase(dbProduct)) {
stmt.execute("CREATE INDEX idx_user_email ON users (email)");
} else if ("PostgreSQL".equalsIgnoreCase(dbProduct)) {
stmt.execute("CREATE INDEX idx_user_email ON users USING btree (email)");
}
./mvnw spring-boot:run
./gradlew bootRun
Flyway will automatically detect and execute migrations on application startup.
Flyway maintains a schema history table named flyway_schema_history
where it records applied migrations.
To inspect:
SELECT * FROM flyway_schema_history;
V1__
, V2__
, etc.
Flyway with Java-based migration support in Spring Boot unlocks fine-grained control over schema evolution and seed data which is perfect for teams with complex DB needs or multi-DB support (like MySQL/PostgreSQL). Whether you're using Maven or Gradle, Flyway integrates seamlessly into your build and CI/CD process.