Software Development

Micronaut Flyway Database Schema Migrations Example

In a modern microservices architecture, managing database schema migrations in a reliable and consistent way is crucial for the smooth evolution of applications. Micronaut Flyway Database Schema Migrations provides a robust solution for handling these migrations efficiently. Flyway, a popular database migration tool, integrates seamlessly with the Micronaut framework, ensuring that schema changes are applied in a version-controlled manner. Let us delve into understanding Micronaut Flyway Database Schema Migrations, explore how to set up and configure these migrations, and walk through building a Micronaut application with Flyway to automate and manage database schema changes.

1. Understanding Micronaut and Flyway

1.1 What is Micronaut?

Micronaut is a modern, JVM-based, full-stack framework specifically designed for building modular, high-performance microservices, serverless applications, and cloud-native systems. It offers first-class support for Java, Kotlin, and Groovy, making it a flexible choice for JVM developers.

Unlike traditional Java frameworks that rely heavily on runtime reflection and proxies (such as Spring), Micronaut uses compile-time annotation processing for dependency injection, AOP (Aspect-Oriented Programming), and configuration. This unique approach drastically reduces memory usage and startup time, making it ideal for microservices and serverless environments like AWS Lambda or Google Cloud Functions.

Micronaut also integrates seamlessly with GraalVM to enable ahead-of-time (AOT) compilation into native executables, further improving performance and resource efficiency.

Key features of Micronaut include:

  • Compile-time dependency injection: No reflection or runtime proxies, leading to better performance and smaller memory footprint.
  • Fast startup time and low memory usage: Ideal for microservices and containerized environments.
  • Seamless integration with GraalVM: Supports building native images for ultra-fast execution and minimal memory use.
  • Reactive and non-blocking: Includes built-in support for reactive programming using RxJava, Project Reactor, etc.
  • Built-in HTTP server/client: Lightweight Netty-based web server and declarative HTTP clients with easy configuration.
  • Cloud-native readiness: Support for service discovery, distributed tracing, configuration management, and more.
  • Modular architecture: Encourages separation of concerns and better maintainability.

1.2 What is Flyway?

Flyway is an open-source database migration tool that enables version control for your database schema. It helps you track, manage, and apply changes to your database schema incrementally and reliably over time. Flyway is designed to be simple yet powerful, and it integrates seamlessly with a wide range of Java frameworks and build tools, including Micronaut, Spring Boot, Maven, Gradle, and more.

Flyway works by executing a series of migration scripts, usually written in SQL, that are stored in a specific directory within your project. These scripts are applied in a consistent and predictable order to ensure that the database structure matches the application code. This makes it especially useful in CI/CD pipelines and for teams practicing DevOps or Agile methodologies.

Each migration script follows a specific naming convention that includes a version number and a description, such as V1__init.sql, V2__add_users_table.sql, and so on. Flyway reads the version number from each file and executes them in ascending order to bring the database schema to the latest state.

Flyway maintains a special metadata table (typically called flyway_schema_history) within your database. This table keeps track of all applied migrations, their version numbers, checksums, execution time, and success status, enabling Flyway to detect new, pending, or failed migrations with ease.

2. Code Example

In this section, we’ll walk through how to implement Flyway migrations in a Micronaut application using a PostgreSQL database. We’ll use Docker to set up PostgreSQL locally and then configure Flyway to manage schema migrations automatically at application startup.

2.1 Set Up PostgreSQL with Docker

To avoid manual installation, we’ll use Docker to quickly spin up a PostgreSQL instance. The following command will pull the official PostgreSQL Docker image and run it with a database named micronaut_db.

docker run --name micronaut-postgres \
  -e POSTGRES_USER=micronaut \
  -e POSTGRES_PASSWORD=micronaut \
  -e POSTGRES_DB=micronaut_db \
  -p 5432:5432 \
  -d postgres:14

2.1.1 Docker Command Explanation

The docker run command creates and starts a new Docker container named micronaut-postgres using the official PostgreSQL 14 image. The -e flags set environment variables inside the container: POSTGRES_USER=micronaut defines the default database user, POSTGRES_PASSWORD=micronaut sets the user’s password, and POSTGRES_DB=micronaut_db initializes a database named micronaut_db. The -p 5432:5432 option maps the container’s PostgreSQL port (5432) to the same port on the host machine, allowing external applications (like a Micronaut service) to connect. The -d option runs the container in detached mode, meaning it runs in the background.

After running this command, the PostgreSQL instance will be accessible at localhost:5432. You can verify it’s running using:

docker ps

Optionally, use pgAdmin or any SQL client like DBeaver to connect and inspect the database schema.

2.2 Add dependencies (build.gradle)

To enable Flyway database migrations and connect to a PostgreSQL database in a Micronaut project, you need to include the necessary dependencies in your build.gradle file. These dependencies allow Micronaut to auto-configure Flyway and connect to the PostgreSQL database driver at runtime.

dependencies {
    implementation("io.micronaut.flyway:micronaut-flyway")
    runtimeOnly("org.postgresql:postgresql")
}

The io.micronaut.flyway:micronaut-flyway dependency integrates Flyway with the Micronaut framework, allowing it to run migration scripts automatically during application startup. It leverages Micronaut’s dependency injection and configuration system for ease of setup. The runtimeOnly("org.postgresql:postgresql") dependency includes the official PostgreSQL JDBC driver, which is required for Micronaut to connect and interact with the PostgreSQL database at runtime. Make sure your project is using a compatible version of Micronaut and Flyway, and don’t forget to refresh your Gradle project after making changes to the dependencies.

2.3 Configuration

After adding the necessary dependencies, you need to configure your Micronaut application to connect to the PostgreSQL database and enable Flyway migrations. This configuration is typically placed in the src/main/resources/application.yml file. The configuration below sets up the default datasource and activates Flyway for that datasource.

datasources:
  default:
    url: jdbc:postgresql://localhost:5432/micronaut_db
    username: micronaut
    password: micronaut
    driverClassName: org.postgresql.Driver

flyway:
  datasources:
    default:
      enabled: true

2.3.1 Configuration Explanation

In this configuration, the datasources.default section defines the connection settings for the PostgreSQL database. The url specifies the JDBC connection string pointing to the local instance of PostgreSQL running on port 5432, with the database name micronaut_db. The username and password match the credentials used when starting the PostgreSQL container in the previous step. The driverClassName tells Micronaut to use the PostgreSQL JDBC driver to communicate with the database.

The flyway.datasources.default.enabled: true line explicitly enables Flyway for the default datasource. Once this is enabled, Flyway will automatically scan the classpath:db/migration directory for migration scripts at application startup and apply any pending migrations. You can also configure additional Flyway options such as locations, baselineOnMigrate, or cleanDisabled if needed, depending on your use case.

2.4 Create SQL Migration Scripts

Flyway looks for migration scripts inside the src/main/resources/db/migration/ directory by default. These scripts must follow a specific naming convention so Flyway can detect and apply them in the correct order. The naming format is typically V<version>__<description>.sql. Below is an example of a versioned SQL migration file named V1__create_books_table.sql which creates a simple books table:

-- V1__create_books_table.sql
CREATE TABLE books (
    id SERIAL PRIMARY KEY,
    title VARCHAR(100) NOT NULL,
    author VARCHAR(100) NOT NULL
);

This script uses standard PostgreSQL syntax. The id column is defined as a SERIAL primary key, which auto-increments automatically. The title and author columns are both VARCHAR(100) and are marked as NOT NULL, ensuring that every record must include a book title and author.

2.5 Book Entity

The following Java class represents the Book entity and is mapped to the books table in the PostgreSQL database. It uses JPA (Java Persistence API) annotations to define the table structure and mapping.

@Entity
@Table(name = "books")
public class Book {

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

    private String title;
    private String author;

    // Getters and setters
}

2.5.1 Class Explanation

The @Entity annotation marks this class as a JPA entity, and @Table(name = "books") specifies the corresponding table name in the database. The id field is annotated with @Id and @GeneratedValue(strategy = GenerationType.IDENTITY) to indicate that it is the primary key and its value should be auto-generated by the database using the identity strategy. The fields title and author correspond to the columns in the books table defined in the Flyway migration script. Ensure that appropriate getter and setter methods are included for serialization, deserialization, and ORM mapping.

2.6 Repository

The repository interface handles all database operations for the Book entity. Micronaut Data automatically generates the implementation at compile time.

@Repository
public interface BookRepository extends CrudRepository<Book, Long> {
}

2.6.1 Class Explanation

The @Repository annotation marks this interface as a Micronaut Data repository bean. By extending CrudRepository<Book, Long>, it inherits basic CRUD operations such as save(), findById(), findAll(), and deleteById(). This allows you to interact with the database without writing boilerplate SQL or JPA code. You can also define custom query methods here using naming conventions or @Query annotations.

2.7 Controller

The controller class exposes HTTP endpoints to interact with the Book entity. It uses Micronaut’s annotation-based approach to handle RESTful requests.

@Controller("/books")
public class BookController {

    private final BookRepository repository;

    public BookController(BookRepository repository) {
        this.repository = repository;
    }

    @Post
    public Book save(@Body Book book) {
        return repository.save(book);
    }

    @Get
    public List<Book> list() {
        return (List<Book>) repository.findAll();
    }
}

2.7.1 Class Explanation

The @Controller("/books") annotation defines a REST controller with a base URI of /books. The constructor uses dependency injection to inject the BookRepository. The @Post method handles HTTP POST requests, accepting a Book object from the request body (annotated with @Body) and saving it to the database. The @Get method returns a list of all books stored in the database by invoking repository.findAll(). This setup allows you to test the API endpoints with tools like Postman or Curl, and quickly verify that Flyway migrations, data access, and REST endpoints are all working as expected.

2.8 Run and Test the Application

Once the database, configuration, migration scripts, and application code are in place, you can start the Micronaut application using the Gradle wrapper command below:

./gradlew run

This command compiles the code, runs any necessary tasks, and launches the Micronaut application. On startup, Micronaut connects to the PostgreSQL database using the datasource configuration defined earlier, and Flyway automatically applies any pending migration scripts (such as creating the books table). You should see Flyway logs in the terminal confirming which migrations were applied.

After the application is running on http://localhost:8080, you can test the REST API endpoints using curl or any HTTP client like Postman or HTTPie.

-- Create a new book record
curl -X POST http://localhost:8080/books \
  -H "Content-Type: application/json" \
  -d '{"title":"Clean Code", "author":"Robert C. Martin"}'

-- Retrieve all book records
curl http://localhost:8080/books

2.8.1. How Flyway Migration Works?

When the Micronaut application starts, the following steps occur as part of the Flyway migration process:

  • On startup, Micronaut initializes the datasource.
  • Flyway reads migration scripts from classpath:db/migration.
  • It checks the flyway_schema_history table to track applied scripts.
  • Unapplied migrations are executed in order.
  • Each successful migration is logged in the history table.
+----+---------+------------------------+----------+---------------------+
| id | version | description            | type     | installed_on        |
+----+---------+------------------------+----------+---------------------+
| 1  | 1       | create_books_table     | SQL      | 2025-05-05 10:23:45 |
+----+---------+------------------------+----------+---------------------+

These steps ensure that the database schema is kept in sync with the application’s evolution over time, automatically applying any necessary changes whenever the application is started.

3. Conclusion

With Micronaut and Flyway, you can automate your database migrations and ensure consistency across environments. This tutorial showed you how to set up PostgreSQL in Docker, configure Flyway in Micronaut, create schema scripts, and build a working API that leverages the auto-migrated database.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button