Flyway Spring Boot Maven - Setup Flyway with Maven

Flyway Spring Boot Maven: Complete Guide for Multi-Environment Database Migrations

Master Flyway Spring Boot Maven setup with this comprehensive tutorial. Learn multi-environment database migrations using Maven commands.

1. Introduction

In the dynamic world of software development, managing database schema changes can be a complex and error-prone task. As your application evolves, so does its database structure, and keeping these changes in sync across different environments is crucial for a stable and reliable system. This is where a powerful database migration tool like Flyway comes to the rescue.

This comprehensive guide will walk you through everything you need to know about using Flyway with Spring Boot Maven for seamless database migrations. We’ll start with the fundamentals and gradually move towards a practical, real-world example of managing environment-specific migrations using Maven.

2. What is Flyway?

Flyway is an open-source database migration tool that simplifies the process of versioning and managing database schema changes. Think of it as “Git for your database” – just like how Git tracks changes in your source code, Flyway tracks changes in your database schema over time.

It follows the principle of “migrations-first,” where every change to the database is represented as a migration script. These scripts are versioned and applied in a specific order, ensuring consistency across all your environments.

3. What are Migrations?

In the context of Flyway, a migration is a script that contains a set of database schema changes. These changes could include creating or altering tables, adding or removing columns, inserting or updating data, and so on. Each migration script has a unique version number, which Flyway uses to track which migrations have been applied and in what order.



4. Why Use Flyway?

Before Flyway: Developers manually ran SQL scripts, often forgetting which changes were applied to which environment, leading to inconsistent database states.

With Flyway: Database changes are versioned, tracked, and automatically applied in the correct order across all environments.

Key Benefits:

  • Version Control: Every database change is versioned and tracked
  • Consistency: Same database state across all environments
  • Automation: Migrations can be configured to run automatically
  • Safety: Built-in validation to prevent conflicts and errors

5. Flyway Migration Script Types

Flyway uses different types of scripts to manage your database changes, each identified by a specific prefix in its filename. Understanding these types is key to effectively versioning your database.

  • Versioned Migrations (V scripts)
    These are the standard migration scripts that run only once to apply schema changes in a specific order. They are perfect for tasks like creating tables or adding columns that should only happen a single time.
  • Repeatable Migrations (R scripts)
    These scripts are re-applied every time their content changes, making them ideal for managing database objects like views or stored procedures. Unlike versioned migrations, they don’t have a version and always run after all pending versioned migrations.
  • Baseline Migrations (B scripts)
    These are used to establish a starting point or “baseline” for a database that already has objects and data. This allows you to bring an existing database under Flyway’s control without having to create migrations for its entire history.
  • Undo Migrations (U scripts)
    Undo scripts, a feature available in the paid editions of Flyway, contain the SQL code to reverse a corresponding versioned migration. They provide a safety net, allowing you to roll back a specific change if a deployment fails.


6. Flyway Migration File Naming Convention

Understanding the proper naming syntax is crucial for Flyway to recognize and execute your migration files correctly through Maven commands. Flyway uses specific patterns to determine the order and type of migrations.

6.1. Versioned Migration (V)

These are the most common type of migration. They run exactly once and are used for incremental, ordered changes to your database schema, like creating a table or adding a column.

Pattern: V{version}__{description}.sql

Components Breakdown:

  • V: Prefix indicating this is a versioned migration (mandatory, uppercase)
  • {version}: Version number (mandatory, must be unique)
  • __: Double underscore separator (mandatory)
  • {description}: Human-readable description (optional but recommended)
  • .sql: File extension (mandatory)

Example: V1__Create_users_table.sql


6.2. Repeatable Migrations (R)

These migrations do not have a version. They are re-applied every time their checksum changes (meaning the file content has been modified). They are perfect for managing database objects whose definitions might change over time, like views, stored procedures, or functions.

Pattern: R__{description}.sql

Components Breakdown:

  • R: A mandatory uppercase prefix indicating a Repeatable migration.
  • __: A mandatory double underscore separator.
  • {description}: A descriptive name for the object being managed.
  • .sql: The file extension.

Example: R__Create_or_update_active_users_view.sql


6.3. Baseline Migrations (B)

A Baseline migration is a special type of script used to establish a starting point for a database, especially one that already exists with objects and data or has a long, complex history of changes. It acts as a consolidated ‘snapshot’ that can bring existing databases under Flyway’s control or allow new environments to start from a specific version without running all historical migrations.

Pattern: B{version}__{description}.sql

Components Breakdown:

  • B: The prefix indicating this is a baseline migration (mandatory, uppercase).
  • {version}: The version number this script represents. This version should align with an equivalent versioned migration.
  • __: Double underscore separator (mandatory).
  • {description}: A human-readable description of the baseline.
  • .sql: The file extension (mandatory).

Example: B1.0__Initial_schema_baseline.sql


6.4. Undo Migrations (U)

Available in Flyway’s paid editions, Undo migrations contain the SQL to reverse a corresponding versioned migration. This enables rollbacks in case of a failed deployment.

Pattern: U{version}__{description}.sql

Components Breakdown:

  • U: The prefix indicating this is an Undo migration (mandatory, uppercase).
  • {version}: The version number. This is the most crucial part, as it must exactly match the version of the V script you intend to reverse.
  • __: Double underscore separator (mandatory).
  • {description}: A human-readable description, which should ideally describe the rollback action.
  • .sql: The file extension (mandatory).

Example:

Let’s say you have a versioned migration to add a new column:

  • Versioned Script: V1.5__Add_phone_number_to_users.sql

The corresponding undo script to remove that column would be:

  • Undo Script: U1.5__Remove_phone_number_from_users.sql

Notice how version 1.5 is identical in both filenames. This is how Flyway knows that U1.5 is the reverse operation for V1.5

FYI: It’s a best practice to never edit a migration script after it has been applied to an environment, as this will cause the checksum to change and validation to fail. If you need to make further changes, create a new migration with a higher version number.


6.5. Flexible Versioning Strategies

For any migration script that requires a version (V, B, and U), Flyway is not opinionated about the format you use, as long as it can be sorted correctly. This flexibility allows you to choose a strategy that best fits your team’s workflow.

Here are some common versioning conventions:

  1. Simple Integer Versions
    Best for simple, linear project timelines.
    • V1__Create_users_table.sql
    • V2__Add_email_column.sql
    • V3__Insert_initial_data.sql
  2. Dotted Versions
    Useful for organizing migrations by feature or release.
    • V1.0__Create_users_table.sql
    • V1.1__Add_email_column.sql
    • V2.0__Create_products_table.sql
  3. Timestamp Versions
    Excellent for large teams where multiple developers might be creating migrations simultaneously, as it minimizes the chance of version number conflicts.
    • V20250915120000__Create_users_table.sql
    • V20250915133000__Add_email_column.sql
    • V20250916090000__Create_products_table.sql
  4. Semantic Versioning (SemVer)
    Aligns your database versioning with your application’s release versioning (MAJOR.MINOR.PATCH).
    • V1.0.0__Initial_schema.sql
    • V1.0.1__Fix_users_constraint.sql
    • V1.1.0__Add_products_feature.sql

7. Understanding Flyway Commands

Flyway provides a set of simple yet powerful commands to manage your database migrations. Here are some of the most common ones:

  • info: Provides a detailed report of all migrations, including their version, description, status (applied, pending, ignored), and when they were applied.
  • migrate: This is the most frequently used command. It scans for available migrations and applies any that are pending (i.e., have not been applied to the database yet).
  • clean: This command will drop all objects (tables, views, etc.) in the configured schemas. Use this with extreme caution, especially in production environments, as it will result in irreversible data loss.
  • baseline: This command is for existing databases or new databases where you want to start tracking from a specific version. It creates Flyway’s schema history table and “baselines” the database at a specific version, telling Flyway to assume all migrations up to that version have already been applied. No actual schema changes are made; it simply marks a starting point.
  • validate: Checks if the applied migrations in the database match the available migration files on the classpath. This is crucial for detecting accidental changes to scripts that have already been run.
  • repair: Helps fix issues with the Flyway schema history table. If a migration fails and leaves the history table in a failed state, repair can remove the entry for the failed migration, allowing you to fix the script and try migrating again.

We will see how to execute these commands using Maven later in this guide.



8. Let’s Get Practical: Flyway Spring Boot Maven

Now, let’s put our knowledge into practice by building a simple Spring Boot application that uses Flyway for database migrations.

Project Structure:

flyway-with-spring-boot-maven
├── src
│   └── main
│       ├── java
│       │   └── com
│       │       └── bootcamptoprod
│       │           ├── controller
│       │           │   └── UserController.java
│       │           ├── entity
│       │           │   └── User.java
│       │           ├── repository
│       │           │   └── UserRepository.java
│       │           └── FlywayMavenDemoApplication.java
│       └── resources
│           ├── application.yml
│           └── db
│               └── migration
│                   ├── common
│                   │   ├── V1__Create_users_table.sql
│                   │   └── V2__Add_email_column.sql
│                   ├── dev
│                   │   └── V3__Insert_dev_test_data.sql
│                   ├── stage
│                   │   └── V3__Insert_stage_test_data.sql
│                   └── prod
│                       └── V3__Insert_prod_test_data.sql
├── config
│   ├── flyway-dev.conf
│   ├── flyway-stage.conf
│   └── flyway-prod.conf
└── pom.xml
Project Structure

🔍 Understanding the Flyway Spring Boot Maven Structure

Here is a quick breakdown of the key files and directories in our project and what each one does. This structure is designed to separate application code, database migrations, and environment configurations cleanly.

🚀 Spring Boot Application (src/main/java)

  • FlywayWithSpringBootMavenApplication.java: The main class that bootstraps and runs the entire Spring Boot application.
  • User.java: A JPA entity that maps to the users database table. The structure of this class must match the schema created by our Flyway scripts.
  • UserRepository.java: A Spring Data JPA repository that provides standard database operations (like findAll(), findById()) for the User entity.
  • UserController.java: The REST controller that exposes /api/users endpoints. We will use this to verify that the data inserted by our Flyway migrations is correctly retrieved from the database.

✈️ Flyway Migrations (src/main/resources/db/migration)

This is the heart of our database versioning. The migrations are organized by environment:

  • /common: This directory contains migration scripts that are universal and must be applied to all environments (dev, stage, and prod). This is where we define the core schema, like creating tables (V1__…) and altering them (V2__…).
  • /dev, /stage, /prod: These directories hold environment-specific scripts. In our case, they each contain a V3__… script to insert different sample or seed data appropriate for that particular environment.

⚙️ Environment Configuration (/config)

Instead of cluttering the pom.xml, we use dedicated .conf files to manage Flyway’s settings for each environment.

  • flyway-dev.conf: Contains the database connection details and settings exclusively for the development environment.
  • flyway-stage.conf: Contains the database connection details and settings for the staging or UAT environment.
  • flyway-prod.conf: Contains the database connection details and settings for the production environment.

📄 Project Configuration Files

  • application.yml: The primary configuration file for the Spring Boot application itself. We use it to configure the data source and, most importantly, to disable Spring Boot’s automatic Flyway integration, giving full control to Maven.
  • pom.xml: This is the master file for our Maven project. It defines the project’s dependencies (like Spring Boot and PostgreSQL), configures the Flyway Maven plugin, and sets up the different profiles (dev, stage, prod) that allow us to target specific environments from the command line.


Let’s set up our project with the necessary dependencies and configurations.

Step 1: Add Maven Dependencies

Every Spring Boot project starts with the pom.xml file. This is where we tell Maven which libraries our project needs to function.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.5</version>
        <relativePath/>
    </parent>

    <groupId>com.bootcamptoprod</groupId>
    <artifactId>flyway-with-spring-boot-maven</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>flyway-with-spring-boot-maven</name>
    <description>A Spring Boot project demonstrating how to configure and run database migrations using the Flyway Maven
        plugin
    </description>

    <properties>
        <java.version>21</java.version>
        <flyway.version>11.12.0</flyway.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-database-postgresql</artifactId>
            <version>${flyway.version}</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <!-- Flyway Maven Plugin Configuration -->
            <plugin>
                <groupId>org.flywaydb</groupId>
                <artifactId>flyway-maven-plugin</artifactId>
                <version>${flyway.version}</version>
                <configuration>
                    <!-- Default configuration - can be overridden by profiles -->
                    <configFiles>
                        <configFile>config/flyway-dev.conf</configFile>
                    </configFiles>
                </configuration>
            </plugin>
        </plugins>
    </build>


    <!-- Maven Profiles for Different Environments -->
    <profiles>
        <profile>
            <id>dev</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.flywaydb</groupId>
                        <artifactId>flyway-maven-plugin</artifactId>
                        <configuration>
                            <configFiles>
                                <configFile>config/flyway-dev.conf</configFile>
                            </configFiles>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>

        <profile>
            <id>stage</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.flywaydb</groupId>
                        <artifactId>flyway-maven-plugin</artifactId>
                        <configuration>
                            <configFiles>
                                <configFile>config/flyway-stage.conf</configFile>
                            </configFiles>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>

        <profile>
            <id>prod</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.flywaydb</groupId>
                        <artifactId>flyway-maven-plugin</artifactId>
                        <configuration>
                            <configFiles>
                                <configFile>config/flyway-prod.conf</configFile>
                            </configFiles>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>
pom.xml

Explanation:

  • <dependencies>: Includes Spring Boot starters, the PostgreSQL driver, and the Flyway libraries.
  • <plugin>: Defines the flyway-maven-plugin, which gives us access to Flyway goals like flyway:migrate and flyway:info directly from the command line.
  • <profiles>: This is where we define Maven profiles for dev, stage, and prod. Each profile tells the Flyway plugin which .conf file to use, allowing us to target different environments with a simple command-line flag (e.g., mvn flyway:migrate -P stage).

Step 2: Configure Application Properties

Next, let’s configure our application properties:

spring:
  application:
    name: flyway-with-spring-boot-maven
  datasource:
    url: jdbc:postgresql://localhost:5432/${DB_NAME:flyway_demo_dev}
    username: ${DB_USER:postgres}
    password: ${DB_PASSWORD:postgres}
    driver-class-name: org.postgresql.Driver

  jpa:
    hibernate:
      ddl-auto: none  # Important: Let Maven Flyway handle all schema changes
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect

  # Flyway completely disabled in Spring Boot - managed by Maven only
  flyway:
    enabled: false
application.yaml

📄 Configuration Overview

This file configures the Spring Boot application, making sure it works hand-in-hand with our Maven-managed Flyway setup.

  • datasource: Sets up the default connection to our PostgreSQL database. It’s configured to use environment variables for the database name, user, and password for flexibility, with sensible defaults provided.
  • jpa.hibernate.ddl-auto: none: This is a critical setting. It tells Hibernate (the JPA provider) not to automatically create or modify database tables, ensuring that Flyway is the single source of truth for all schema changes.
  • flyway.enabled: false: This is the most important instruction. It completely disables Spring Boot’s built-in Flyway integration, giving us full, explicit control over when and how migrations are run through the Flyway Maven plugin.


Step 3: Application Entry Point

This is the main class that bootstraps our entire application.

package com.bootcamptoprod;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class FlywayWithSpringBootMavenApplication {

    public static void main(String[] args) {
        SpringApplication.run(FlywayWithSpringBootMavenApplication.class, args);
    }

}
FlywayWithSpringBootMavenApplication.java

Explanation:

  • Main Class to Run the Application: FlywayWithSpringBootMavenApplication is the starting point of our application. When you run this class, Spring Boot initializes all components and starts the embedded server.

Step 4: Create User Entity

This class acts as a blueprint for a user. It defines the structure of a user record, with fields like id, name, and email, and maps directly to the columns in our users database table.

package com.bootcamptoprod.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(unique = true)
    private String email;

    // Constructors
    // Getters and Setters
}
User.java

Step 5: The User Repository

This interface is our data access layer. It provides all the necessary methods to interact with the users table in the database, such as finding, saving, and deleting user records, without us having to write the actual SQL code.

package com.bootcamptoprod.repository;

import com.bootcamptoprod.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
UserRepository.java


Step 6: The Controller Layer: API Endpoint

This controller class will expose our GET endpoints that return the data that is stored inside our database.

package com.bootcamptoprod.controller;

import com.bootcamptoprod.entity.User;
import com.bootcamptoprod.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserRepository userRepository;

    @GetMapping
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        return userRepository.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }
}
UserController.java

Explanation:

  • This class is the public face of our application’s API, defining the web endpoints for user data.
  • It exposes two distinct GET endpoints: one to retrieve a complete list of all users, and another to fetch a single, specific user by their ID.

Step 7: Configuring the Flyway Maven Plugin

To manage our different environments, we use dedicated configuration files. This approach keeps our settings clean and separate from the pom.xml, making it easy to manage database connections and migration paths for each specific environment.

flyway-dev.conf

# Database connection
flyway.url=jdbc:postgresql://localhost:5432/flyway_demo_dev
flyway.user=postgres
flyway.password=postgres

# Migration locations
flyway.locations=classpath:db/migration/common,classpath:db/migration/dev

# Validation settings
flyway.validateOnMigrate=true
flyway.cleanDisabled=false
flyway.baselineOnMigrate=true

# Schema settings
flyway.defaultSchema=public
flyway.schemas=public
flyway-dev.conf

This file is tailored for local and dev development. It connects to the flyway_demo_dev database and crucially tells Flyway to look for SQL scripts in both the common and dev folders, ensuring developers get the base schema plus their specific test data. For convenience, the clean command is enabled (cleanDisabled=false) to allow for quick database resets during development.


flyway-stage.conf

# Database connection
flyway.url=jdbc:postgresql://localhost:5432/flyway_demo_stage
flyway.user=postgres
flyway.password=postgres

# Migration locations
flyway.locations=classpath:db/migration/common,classpath:db/migration/stage

# Validation settings
flyway.validateOnMigrate=true
flyway.cleanDisabled=true
flyway.baselineOnMigrate=true

# Schema settings
flyway.defaultSchema=public
flyway.schemas=public
flyway-stage.conf

This configuration is for a pre-production or UAT environment. It points to a dedicated staging database and combines SQL scripts from the common and stage folders. To protect against accidental data loss in this shared environment, the clean command is disabled (cleanDisabled=true).


flyway-prod.conf

# Database connection
flyway.url=jdbc:postgresql://localhost:5432/flyway_demo_prod
flyway.user=postgres
flyway.password=postgres

# Migration locations
flyway.locations=classpath:db/migration/common,classpath:db/migration/prod

# Validation settings
flyway.validateOnMigrate=true
flyway.cleanDisabled=true
flyway.baselineOnMigrate=true

# Schema settings
flyway.defaultSchema=public
flyway.schemas=public
flyway-prod.conf

This is the configuration for our live production environment. It points to the production database and uses migrations from the common and prod folders. For maximum safety and to prevent catastrophic mistakes, the clean command is strictly disabled (cleanDisabled=true).


Step 8: The Multi-Environment Migration Structure

A key to robust database management is separating universal schema changes from environment-specific data. Our project achieves this by organizing SQL migration scripts into distinct folders, which Maven can then selectively apply based on the active profile. This ensures a consistent base schema everywhere, while allowing for tailored data in each environment.

8.1. /common Scripts (The Foundation)

This folder contains the core, foundational schema changes that must be applied to all environments in a precise order.

  • V1__Create_users_table.sql: This is the first migration. It builds the initial users table with its primary key and essential columns.
  • V2__Add_email_column.sql: This script alters the existing users table to add a new email column with a unique constraint, a change needed across every environment.
-- Create users table
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
V1__Create_users_table.sql
-- Add email column with unique constraint
ALTER TABLE users ADD COLUMN email VARCHAR(255);

-- Add unique constraint
ALTER TABLE users ADD CONSTRAINT users_email_unique UNIQUE (email);
V2__Add_email_column.sql

8.2. /dev Scripts (For Local Development)

This folder contains migrations that run only in the development environment, after all the common scripts.

  • V3__Insert_dev_test_data.sql: This script populates the database with a generous amount of mock data (e.g., ‘John Doe Dev’, ‘Jane Smith Dev’). This is perfect for local testing, allowing developers to work with the API without having to manually insert records.
-- Insert development test data
INSERT INTO users (name, email) VALUES
    ('John Doe Dev', 'john.doe@dev.local'),
    ('Jane Smith Dev', 'jane.smith@dev.local'),
    ('Bob Johnson Dev', 'bob.johnson@dev.local'),
    ('Alice Brown Dev', 'alice.brown@dev.local'),
    ('Charlie Wilson Dev', 'charlie.wilson@dev.local');

-- Insert admin user for development
INSERT INTO users (name, email) VALUES
    ('Dev Admin', 'admin@dev.local');
V3__Insert_dev_test_data.sql

8.3. /stage Scripts (For QA and UAT)

This folder is for the staging or User Acceptance Testing environment. The data here is typically more realistic and curated for testers.

  • V3__Insert_stage_test_data.sql: This script inserts a smaller, more specific set of test data (e.g., ‘Stage Test User 1’, ‘Stage QA User’) suitable for the QA team to validate new features.
-- Insert staging test data
INSERT INTO users (name, email) VALUES
    ('Stage Test User 1', 'testuser1@staging.com'),
    ('Stage Test User 2', 'testuser2@staging.com'),
    ('Stage QA User', 'qa@staging.com');

-- Insert stage admin
INSERT INTO users (name, email) VALUES
    ('Stage Admin', 'admin@staging.com');
V3__Insert_stage_test_data.sql

8.4. /prod Scripts (For Production)

This folder is for the live production environment. These scripts should never contain test data but might be used for initial data setup.

  • V3__Insert_prod_test_data.sql: In a real-world scenario, this script would insert essential seed data, such as an initial administrative user (‘Prod Admin’) or default application settings required for the system to function correctly upon first launch.
-- Insert prod test data
INSERT INTO users (name, email) VALUES
    ('Prod Test User 1', 'testuser1@prod.com'),
    ('Prod Test User 2', 'testuser2@prod.com'),
    ('Prod QA User', 'qa@prod.com');

-- Insert prod admin
INSERT INTO users (name, email) VALUES
    ('Prod Admin', 'admin@prod.com');
V3__Insert_prod_test_data.sql

Because each environment has its own V3 script, our Maven profiles ensure that only the V3 script from the active profile’s folder is executed, preventing data from one environment from ever leaking into another.



9. How It All Works Together

The entire process is designed to be deliberate and controlled, ensuring that database changes are managed separately from the application’s lifecycle. Here is the step-by-step flow:

  1. Developer Initiates Migration: A developer decides to set up or update the database for a specific environment. They run a Maven command from the terminal, such as mvn flyway:migrate -P stage.
  2. Maven Profile Activation: The -P stage flag activates the “stage” profile defined in the pom.xml. This tells the flyway-maven-plugin which configuration to use.
  3. Flyway Configuration Loaded: The plugin reads the corresponding configuration file, config/flyway-stage.conf. This file provides the database URL, credentials, and most importantly, the migration script locations (common and stage folders).
  4. Database Migration: Flyway connects to the staging database. It scans the specified folders, checks its flyway_schema_history table to see what’s already been applied, and executes any pending SQL scripts in version order.
  5. Application Startup: Independently, the Spring Boot application is started. Because of the flyway.enabled: false setting in application.yml, it makes no attempt to run migrations itself.
  6. Connecting to the Schema: The application connects to the database, which has already been prepared by the Maven command. Thanks to hibernate.ddl-auto: none, it trusts that the schema is correct and does not try to alter it.
  7. API Request and Verification: When a request hits the /api/users endpoint, the UserController queries the database via the UserRepository. It successfully retrieves and returns the data that was inserted by the combination of common and stage migration scripts, confirming the entire process worked as intended.


10. Flyway Commands with Maven Deep Dive

All Flyway operations are performed through Maven commands using our configured profiles.

10.1. flyway:info – Check Migration Status

Purpose: Shows the status of all migrations (applied, pending, failed)

When to Use:

  • Before running migrations to check current state
  • After migrations to verify success
  • Troubleshooting database issues

Usage:

# Development environment
mvn flyway:info -Pdev

# Staging environment
mvn flyway:info -Pstage

# Production environment
mvn flyway:info -Pprod

# Alternative: Direct config file specification
mvn flyway:info -Dflyway.configFiles=config/flyway-dev.conf
Terminal

Sample Output:

[INFO] ----------< com.bootcamptoprod:flyway-with-spring-boot-maven >----------
[INFO] Building flyway-with-spring-boot-maven 0.0.1-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- flyway:11.12.0:info (default-cli) @ flyway-with-spring-boot-maven ---
[INFO] Database: jdbc:postgresql://localhost:5432/flyway_demo_dev (PostgreSQL 17.4)
[INFO] Schema history table "public"."flyway_schema_history" does not exist yet
[INFO] Schema version: << Empty Schema >>
[INFO] 
[INFO] +-----------+---------+----------------------+------+--------------+---------+----------+
| Category  | Version | Description          | Type | Installed On | State   | Undoable |
+-----------+---------+----------------------+------+--------------+---------+----------+
| Versioned | 1       | Create users table   | SQL  |              | Pending | No       |
| Versioned | 2       | Add email column     | SQL  |              | Pending | No       |
| Versioned | 3       | Insert dev test data | SQL  |              | Pending | No       |
+-----------+---------+----------------------+------+--------------+---------+----------+
Terminal

10.2. flyway:migrate – Apply Migrations

Purpose: Applies all pending migrations to the database

When to Use:

  • Initial database setup
  • Deploying new schema changes
  • Updating existing databases

Usage:

# Apply migrations to development
mvn flyway:migrate -Pdev

# Apply migrations to staging
mvn flyway:migrate -Pstage

# Apply migrations to production
mvn flyway:migrate -Pprod

# Target specific version (stop at version 2)
mvn flyway:migrate -Pdev -Dflyway.target=2

# Verbose output for troubleshooting
mvn flyway:migrate -Pdev -X


# Advanced Options

# Skip validation before migration
mvn flyway:migrate -Pdev -Dflyway.validateOnMigrate=false

# Baseline existing database at version 1
mvn flyway:migrate -Pdev -Dflyway.baselineOnMigrate=true -Dflyway.baselineVersion=1
Terminal

Sample Output:

[INFO] ----------< com.bootcamptoprod:flyway-with-spring-boot-maven >----------
[INFO] Building flyway-with-spring-boot-maven 0.0.1-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- flyway:11.12.0:migrate (default-cli) @ flyway-with-spring-boot-maven ---
[INFO] Database: jdbc:postgresql://localhost:5432/flyway_demo_dev (PostgreSQL 17.4)
[INFO] Schema history table "public"."flyway_schema_history" does not exist yet
[INFO] Successfully validated 3 migrations (execution time 00:00.014s)
[INFO] All configured schemas are empty; baseline operation skipped. A baseline or migration script with a lower version than the baseline version may execute if available. Check the Schemas parameter if this is not intended.
[INFO] Creating Schema History table "public"."flyway_schema_history" ...
[INFO] Current version of schema "public": << Empty Schema >>
[INFO] Migrating schema "public" to version "1 - Create users table"
[INFO] Migrating schema "public" to version "2 - Add email column"
[INFO] Migrating schema "public" to version "3 - Insert dev test data"
[INFO] Successfully applied 3 migrations to schema "public", now at version v3 (execution time 00:00.010s)
[INFO] ------------------------------------------------------------------------
Terminal

10.3. flyway:validate – Validate Migration Integrity

Purpose: Validates applied migrations against available migration files

What it Validates:

  • Migration files haven’t been modified
  • No missing migration files
  • Checksums match applied migrations
  • Proper version sequence ordering

When to Use:

  • Before production deployments
  • In CI/CD pipeline quality gates
  • When database state looks suspicious
  • Regular health checks

Usage:

# Validate development database
mvn flyway:validate -Pdev
Terminal

Sample Output:

[INFO] ----------< com.bootcamptoprod:flyway-with-spring-boot-maven >----------
[INFO] Building flyway-with-spring-boot-maven 0.0.1-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- flyway:11.12.0:validate (default-cli) @ flyway-with-spring-boot-maven ---
[INFO] Database: jdbc:postgresql://localhost:5432/flyway_demo_dev (PostgreSQL 17.4)
[INFO] Successfully validated 3 migrations (execution time 00:00.015s)
[INFO] ------------------------------------------------------------------------
Terminal (Successful Scenario)
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.flywaydb:flyway-maven-plugin:11.12.0:validate (default-cli) on project flyway-with-spring-boot-maven: org.flywaydb.core.api.exception.FlywayValidateException: Validate failed: Migrations have failed validation                                                                                                                                                                                                                                
[ERROR] Migration checksum mismatch for migration version 3
[ERROR] -> Applied to database : 348766758
[ERROR] -> Resolved locally    : 1709153961
[ERROR] Either revert the changes to the migration, or run repair to update the schema history.
[ERROR] Need more flexibility with validation rules? Learn more: https://rd.gt/3AbJUZE
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
Terminal (Failure Scenario)


10.4. flyway:repair – Fix Schema History

Purpose: Repairs the schema history table by removing failed migrations and updating checksums

What it Does:

  • Removes failed migration entries from schema history
  • Realigns checksums for manually fixed migrations
  • Marks missing migrations as deleted
  • Cleans up inconsistent states

When to Use:

  • After fixing failed migrations manually
  • When checksums don’t match due to manual database fixes
  • Emergency database state repairs

Usage:

# Repair development database
mvn flyway:repair -Pdev

# Repair with verbose logging
mvn flyway:repair -Pdev -X
Terminal

Sample Output:

[INFO] ----------< com.bootcamptoprod:flyway-with-spring-boot-maven >----------
[INFO] Building flyway-with-spring-boot-maven 0.0.1-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- flyway:11.12.0:repair (default-cli) @ flyway-with-spring-boot-maven ---
[INFO] Database: jdbc:postgresql://localhost:5432/flyway_demo_dev (PostgreSQL 17.4)
[INFO] Repair of failed migration in Schema History table "public"."flyway_schema_history" not necessary. No failed migration detected.
[INFO] Repairing Schema History table for version 3 (Description: Insert dev test data, Type: SQL, Checksum: 1709153961)  ...
[INFO] Successfully repaired schema history table "public"."flyway_schema_history" (execution time 00:00.037s).
[INFO] ------------------------------------------------------------------------
Terminal

⚠️ Caution: Use carefully, especially in production environments


10.5. flyway:clean – Reset Database Schema

Purpose: Drops all objects in the configured schemas

When to Use:

  • Fresh development environment setup
  • Integration test cleanup
  • NEVER in staging or production (disabled in our configs)

Usage:

# Clean development database (enabled in dev config)
mvn flyway:clean -Pdev

# Clean is disabled by default in stage/prod for safety
mvn flyway:clean -Pstage  # This will fail due to cleanDisabled=true
Terminal

Sample Output:

[INFO] ----------< com.bootcamptoprod:flyway-with-spring-boot-maven >----------
[INFO] Building flyway-with-spring-boot-maven 0.0.1-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- flyway:11.12.0:clean (default-cli) @ flyway-with-spring-boot-maven ---
[INFO] Database: jdbc:postgresql://localhost:5432/flyway_demo_dev (PostgreSQL 17.4)
[INFO] Successfully dropped pre-schema database level objects (execution time 00:00.000s)
[INFO] Successfully cleaned schema "public" (execution time 00:00.104s)
[INFO] Successfully cleaned schema "public" (execution time 00:00.029s)
[INFO] Successfully dropped post-schema database level objects (execution time 00:00.000s)
[INFO] ------------------------------------------------------------------------
Terminal

10.6. flyway:baseline – Initialize Existing Database

Purpose: Baselines an existing database at a specific version

When to Use:

  • Adding Flyway to existing databases
  • Starting migration tracking from a specific version
  • Database import scenarios

Usage:

# Baseline at version 1
mvn flyway:baseline -Pdev -Dflyway.baselineVersion=1 -Dflyway.baselineDescription="Initial baseline"

# Baseline with custom description
mvn flyway:baseline -Pdev -Dflyway.baselineVersion=0 -Dflyway.baselineDescription="Legacy system baseline"
Terminal

Sample Output:

[INFO] ----------< com.bootcamptoprod:flyway-with-spring-boot-maven >----------
[INFO] Building flyway-with-spring-boot-maven 0.0.1-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- flyway:11.12.0:baseline (default-cli) @ flyway-with-spring-boot-maven ---
[INFO] Database: jdbc:postgresql://localhost:5432/flyway_demo_dev (PostgreSQL 17.4)
[INFO] Creating Schema History table "public"."flyway_schema_history" with baseline ...
[INFO] Successfully baselined schema with version: 1
[INFO] ------------------------------------------------------------------------
Terminal


11. Maven Profiles vs. Direct Configuration

To tell Flyway which environment to target, you have two primary methods. While both achieve the same result, using Maven profiles is the standard and recommended approach for its clarity and maintainability.

Using Maven Profiles (Recommended)

This is the cleanest and most robust method. By defining profiles in your pom.xml, you create simple, memorable shortcuts that abstract away the underlying file paths.

mvn flyway:migrate -Pdev
mvn flyway:migrate -Pstage  
mvn flyway:migrate -Pprod
Terminal

Why it’s better:

  • Clarity: The command is short and its intent is immediately obvious (-Pdev clearly targets the dev environment).
  • Consistency: The mapping of a profile to its configuration file is defined once in the pom.xml, ensuring the entire team uses the same convention.
  • Less Prone to Error: You avoid typing long, error-prone file paths in the command line, reducing the chance of mistakes.

Using Direct Configuration (Command-Line Override)

This method uses the -D flag to directly pass a configuration parameter to the Maven plugin, overriding any defaults set in the pom.xml.

mvn flyway:migrate -Dflyway.configFiles=config/flyway-dev.conf
mvn flyway:migrate -Dflyway.configFiles=config/flyway-stage.conf
mvn flyway:migrate -Dflyway.configFiles=config/flyway-prod.conf
Terminal

When to use it:

  • Flexibility: It’s useful for quick, one-off tests with a temporary configuration file without needing to modify the pom.xml.

12. Putting It All to the Test: A Development Workflow

Now that our project is fully configured, let’s walk through the end-to-end process of migrating our development database and verifying the results through our Spring Boot application’s API.

Prerequisite: Ensure you have a PostgreSQL database named flyway_demo_dev running and accessible, as this is the target database specified in our flyway-dev.conf file.

Step 1: Check the Initial State

Before applying any changes, it’s a best practice to inspect the database’s current state. Since this is a fresh database, Flyway should report that no migrations have been applied yet.

# Check the initial status of the dev database
mvn flyway:info -Pdev
Terminal

Expected Output: You will see a message indicating that the flyway_schema_history table doesn’t exist yet, which is exactly what we expect.

[INFO] ----------< com.bootcamptoprod:flyway-with-spring-boot-maven >----------
[INFO] Building flyway-with-spring-boot-maven 0.0.1-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- flyway:11.12.0:info (default-cli) @ flyway-with-spring-boot-maven ---
[INFO] Database: jdbc:postgresql://localhost:5432/flyway_demo_dev (PostgreSQL 17.4)
[INFO] Schema history table "public"."flyway_schema_history" does not exist yet
[INFO] Schema version: << Empty Schema >>
[INFO] 
[INFO] +-----------+---------+----------------------+------+--------------+---------+----------+
| Category  | Version | Description          | Type | Installed On | State   | Undoable |
+-----------+---------+----------------------+------+--------------+---------+----------+
| Versioned | 1       | Create users table   | SQL  |              | Pending | No       |
| Versioned | 2       | Add email column     | SQL  |              | Pending | No       |
| Versioned | 3       | Insert dev test data | SQL  |              | Pending | No       |
+-----------+---------+----------------------+------+--------------+---------+----------+

[INFO] ------------------------------------------------------------------------
Terminal

Step 2: Run the Development Migration

Now, we’ll execute the migrate command. The Maven profile -Pdev ensures that Flyway uses flyway-dev.conf, which correctly targets the scripts in both the /common and /dev folders.

# Apply all pending migrations for the dev environment
mvn flyway:migrate -Pdev
Terminal

Expected Output: The console will show Flyway applying the three relevant migrations in order: V1 and V2 from common, and the specific V3 script from dev.

[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- flyway:11.12.0:migrate (default-cli) @ flyway-with-spring-boot-maven ---
[INFO] Database: jdbc:postgresql://localhost:5432/flyway_demo_dev (PostgreSQL 17.4)
[INFO] Schema history table "public"."flyway_schema_history" does not exist yet
[INFO] Successfully validated 3 migrations (execution time 00:00.011s)
[INFO] All configured schemas are empty; baseline operation skipped. A baseline or migration script with a lower version than the baseline version may execute if available. Check the Schemas parameter if this is not intended.
[INFO] Creating Schema History table "public"."flyway_schema_history" ...
[INFO] Current version of schema "public": << Empty Schema >>
[INFO] Migrating schema "public" to version "1 - Create users table"
[INFO] Migrating schema "public" to version "2 - Add email column"
[INFO] Migrating schema "public" to version "3 - Insert dev test data"
[INFO] Successfully applied 3 migrations to schema "public", now at version v3 (execution time 00:00.012s)
[INFO] ------------------------------------------------------------------------
Terminal

Step 3: Verify the Final State

After the migration, we run info again. This time, it should produce a clean report showing all three migrations have been successfully applied.

# Verify the final status of the dev database
mvn flyway:info -Pdev
Terminal

Expected Output: A table will be displayed confirming the successful application of each script.

[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- flyway:11.12.0:info (default-cli) @ flyway-with-spring-boot-maven ---
[INFO] Database: jdbc:postgresql://localhost:5432/flyway_demo_dev (PostgreSQL 17.4)
[INFO] Schema version: 3
[INFO] 
[INFO] +-----------+---------+----------------------+------+---------------------+---------+----------+
| Category  | Version | Description          | Type | Installed On        | State   | Undoable |
+-----------+---------+----------------------+------+---------------------+---------+----------+
| Versioned | 1       | Create users table   | SQL  | 2025-09-14 16:02:45 | Success | No       |
| Versioned | 2       | Add email column     | SQL  | 2025-09-14 16:02:45 | Success | No       |
| Versioned | 3       | Insert dev test data | SQL  | 2025-09-14 16:02:45 | Success | No       |
+-----------+---------+----------------------+------+---------------------+---------+----------+

[INFO] ------------------------------------------------------------------------
Terminal

Step 4: Run the Application and Test the API

With the database schema created and populated, the final step is to run our Spring Boot application and hit the API endpoint to see the data.

  • Start the Application: mvn spring-boot:run
  • Test the REST Endpoint: curl http://localhost:8080/api/users
  • Expected JSON Response: The API should return a JSON array containing the exact users we inserted in our V3__Insert_dev_test_data.sql script. This is the ultimate confirmation that our environment-specific migration workflow is functioning perfectly.
[
  {
    "id": 1,
    "name": "John Doe Dev",
    "email": "john.doe@dev.local"
  },
  {
    "id": 2,
    "name": "Jane Smith Dev",
    "email": "jane.smith@dev.local"
  },
  {
    "id": 3,
    "name": "Bob Johnson Dev",
    "email": "bob.johnson@dev.local"
  },
  {
    "id": 4,
    "name": "Alice Brown Dev",
    "email": "alice.brown@dev.local"
  },
  {
    "id": 5,
    "name": "Alice Brown Dev",
    "email": "alice.brown@dev.local123"
  },
  {
    "id": 6,
    "name": "Charlie Wilson Dev",
    "email": "charlie.wilson@dev.local"
  },
  {
    "id": 7,
    "name": "Dev Admin",
    "email": "admin@dev.local"
  }
]
API Response


13. Source Code

The complete source code for this Flyway Spring Boot Maven multi-environment migration project is available on GitHub. The repository includes all the configuration files, migration scripts, Maven profiles, and Spring Boot application code demonstrated in this tutorial. Simply clone the repository, configure your PostgreSQL database connections in the flyway.conf files, and run the Maven commands to see the multi-environment migration system in action.

🔗 Flyway with Spring Boot Maven: https://github.com/BootcampToProd/flyway-with-spring-boot-maven

14. Things to Consider

When implementing Flyway with Maven for multi-environment database migrations, keep these critical factors in mind:

  1. Environment Variable Security: Never hardcode database passwords in flyway.conf files. Use environment variables or external configuration management tools, especially for staging and production environments.
  2. Migration File Immutability: Once a migration has been applied to any environment beyond development, never modify the file. Create new versioned migrations instead to maintain checksum integrity across all environments.
  3. Backup Strategy: Always create database backups before running migrations in staging and production. Implement automated backup procedures as part of your deployment pipeline.
  4. Permission Management: Use dedicated database users with minimal required permissions for migrations. Avoid using superuser accounts, especially in production environments.
  5. Migration Testing: Test all migrations thoroughly in development and staging environments before applying to production. Consider using database copies or snapshots for testing complex migrations.
  6. Rollback Planning: While Flyway Community doesn’t support automatic rollbacks, maintain manual rollback procedures and scripts for critical migrations, especially schema changes.


15. FAQs

Why use Maven commands instead of Spring Boot’s built-in Flyway integration?

Can I use this approach with databases other than PostgreSQL?

What happens if a migration fails in production?

Can I run migrations as part of the Maven build lifecycle?

How do I migrate an existing database to use Flyway?

16. Conclusion

The Maven-driven approach to Flyway integration with Spring Boot provides enterprise-grade database migration management with precise environmental control and team collaboration benefits. By separating migration logic from application startup and leveraging Maven profiles with dedicated configuration files, development teams achieve consistent, reliable, and audit-friendly database evolution across all environments. This approach scales effectively from small projects to large enterprise applications requiring strict change control and regulatory compliance.



17. Learn More

#

Interested in learning more?

Spring AI Text-to-Speech Streaming with ElevenLabs: Instant Audio Playback



Add a Comment

Your email address will not be published.