Pre-release · 0.1.0-SNAPSHOT

Migrations generated from your Hibernate entities.

WaveBase brings the Entity Framework / Django workflow to the JVM. Change an @Entity, run one task, and get a versioned Migration with reviewable SQL for PostgreSQL, MySQL and H2. Gradle and Maven plugins included.

01Edit entitiesYour @Entity classes are the source of truth.
02GenerateDiff against the committed Model Snapshot.
03Review SQLup.sql / down.sql per dialect.
04ApplyAutomatically, or by a DBA and then marked.

Five ideas to know

Everything WaveBase does is built on these. The full glossary ships with the source code.

Entity Model

The @Entity classes in your main source set, read via reflection after compilation.

Model Snapshot

A committed JSON file holding the last known Entity Model. The baseline every diff is computed against.

src/main/resources/db/model-snapshot.json

Migration

A versioned folder with plain SQL per dialect. A missing down.sql means it is irreversible on that dialect.

db/migration/<version>/postgresql.up.sql

Schema History Table

Lives in the database and records which Migrations are applied there. Created on first use.

wavebase_schema_history

Reverse Scaffold

One-shot generation of entity classes from an existing database, for onboarding legacy schemas.

Quickstart

WaveBase works with Gradle and Maven: pick yours, and every example on this page follows. It is not on Maven Central or the Gradle Plugin Portal yet, so until the first release you build it from a local checkout.

The source repository opens soon. It is still private while the first release is validated. The steps below show the setup as it will work once it is public. Follow the organization to be notified.
  1. Clone WaveBase next to your project

    git clone https://github.com/WaveBase-Project/WaveBase.git ../WaveBase
  2. Include the build and apply the plugin

    settings.gradle.kts
    pluginManagement {
        includeBuild("../WaveBase")
    }
    build.gradle.kts
    import io.github.agallina.wavebase.core.dialect.Dialect
    
    plugins {
        java
        id("io.github.agallina.wavebase")
    }
    
    dependencies {
        runtimeOnly("org.postgresql:postgresql:42.7.3")  // your JDBC driver
    }
    
    wavebase {
        jdbcUrl.set(providers.environmentVariable("WAVEBASE_JDBC_URL"))
        jdbcUsername.set(providers.environmentVariable("WAVEBASE_JDBC_USER"))
        jdbcPassword.set(providers.environmentVariable("WAVEBASE_JDBC_PASSWORD"))
        dialect.set(Dialect.POSTGRESQL)  // or MYSQL, H2
    }
  3. Generate your first Migration

    With no snapshot yet, the first Migration creates every table, ordered so foreign keys come after the tables they reference.

    ./gradlew wavebaseGenerateMigration
    # WaveBase: generated Migration 20261010143005123001 in src/main/resources/db/migration/...
  4. Review, commit, apply

    git add src/main/resources/db
    git commit -m "Initial schema"
    ./gradlew wavebaseApply
  1. Install WaveBase on your machine (once)

    One command builds WaveBase and puts it in your local Maven repository (~/.m2). You only need Java 17: the ./gradlew script downloads everything else by itself. This step goes away once WaveBase is on Maven Central.

    git clone https://github.com/WaveBase-Project/WaveBase.git
    cd WaveBase
    ./gradlew publishToMavenLocal
  2. Add WaveBase to your pom.xml

    Two blocks. First, the settings, in <properties>. Change the dialect to match your database; leave the rest as is.

    pom.xml · inside <project>
    <properties>
      <!-- ...your existing properties... -->
    
      <wavebase.version>0.1.0-SNAPSHOT</wavebase.version>
      <wavebase.dialect>POSTGRESQL</wavebase.dialect>   <!-- POSTGRESQL, MYSQL or H2 -->
      <wavebase.jdbcUrl>${env.WAVEBASE_JDBC_URL}</wavebase.jdbcUrl>
      <wavebase.jdbcUsername>${env.WAVEBASE_JDBC_USER}</wavebase.jdbcUsername>
      <wavebase.jdbcPassword>${env.WAVEBASE_JDBC_PASSWORD}</wavebase.jdbcPassword>
    </properties>

    Then the plugin, in <build><plugins>. If your pom has no <build> section yet, paste the whole block.

    pom.xml · inside <project>
    <build>
      <plugins>
        <plugin>
          <groupId>io.github.agallina</groupId>
          <artifactId>wavebase-maven-plugin</artifactId>
          <version>${wavebase.version}</version>
          <executions>
            <execution>
              <goals>
                <goal>verify-migrations</goal>   <!-- checks every mvn verify / mvn install -->
              </goals>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
  3. Make sure the JDBC driver is a dependency

    Most projects already have it. If not, add the one for your database:

    Databasewavebase.dialectDriver dependency
    PostgreSQLPOSTGRESQLorg.postgresql:postgresql:42.7.3
    MySQLMYSQLcom.mysql:mysql-connector-j:8.4.0
    H2H2com.h2database:h2:2.2.224
    Show a complete pom.xml to start from
    pom.xml
    <?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>
    
      <groupId>com.example</groupId>
      <artifactId>my-app</artifactId>
      <version>1.0.0-SNAPSHOT</version>
    
      <properties>
        <maven.compiler.release>17</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    
        <!-- WaveBase -->
        <wavebase.version>0.1.0-SNAPSHOT</wavebase.version>
        <wavebase.dialect>POSTGRESQL</wavebase.dialect>
        <wavebase.jdbcUrl>${env.WAVEBASE_JDBC_URL}</wavebase.jdbcUrl>
        <wavebase.jdbcUsername>${env.WAVEBASE_JDBC_USER}</wavebase.jdbcUsername>
        <wavebase.jdbcPassword>${env.WAVEBASE_JDBC_PASSWORD}</wavebase.jdbcPassword>
      </properties>
    
      <dependencies>
        <!-- Your entities: Hibernate 6 / Jakarta Persistence -->
        <dependency>
          <groupId>org.hibernate.orm</groupId>
          <artifactId>hibernate-core</artifactId>
          <version>6.5.2.Final</version>
        </dependency>
    
        <!-- JDBC driver for your database -->
        <dependency>
          <groupId>org.postgresql</groupId>
          <artifactId>postgresql</artifactId>
          <version>42.7.3</version>
          <scope>runtime</scope>
        </dependency>
      </dependencies>
    
      <build>
        <plugins>
          <plugin>
            <groupId>io.github.agallina</groupId>
            <artifactId>wavebase-maven-plugin</artifactId>
            <version>${wavebase.version}</version>
            <executions>
              <execution>
                <goals>
                  <goal>verify-migrations</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </project>
  4. Tell WaveBase where your database is

    The pom reads the connection from environment variables, so no password ends up in git. Generating Migrations does not need them; applying them does.

    export WAVEBASE_JDBC_URL=jdbc:postgresql://localhost:5432/mydb
    export WAVEBASE_JDBC_USER=myuser
    export WAVEBASE_JDBC_PASSWORD=secret
  5. Generate your first Migration

    Run compile in the same command: WaveBase reads your compiled entity classes. With no snapshot yet, the first Migration creates every table.

    mvn compile wavebase:generate-migration
    # [INFO] WaveBase: generated Migration 20261010143005123001 in src/main/resources/db/migration/...
  6. Review, commit, apply

    git add src/main/resources/db
    git commit -m "Initial schema"
    mvn wavebase:apply

    From now on, mvn verify and mvn install fail if an entity changes without a Migration, so nobody forgets one.

Maven questions

A different database per environment?

Any setting can be overridden on the command line, for example mvn wavebase:apply -Dwavebase.jdbcUrl=jdbc:postgresql://staging/mydb. Maven profiles work too.

No environment variables?

Write the values directly in <properties>. Fine for a local H2 or Docker database; keep real passwords out of git.

Skip the check for one build?

mvn install -Dwavebase.verify.enabled=false. Useful for quick local builds; keep it on in CI.

Why wavebase: works

The short prefix resolves because the plugin is declared in your pom. Without it, use io.github.agallina:wavebase-maven-plugin:0.1.0-SNAPSHOT:<goal>.

Workflows

The everyday loop is the same for everyone. What differs is who runs the SQL against the database.

The everyday loop

// 1. change an entity
@Entity @Table(name = "posts")
public class Post {
    @Id private Long id;
    @Column(nullable = false) private String title;
    @Column private String summary;            // new
    @ManyToOne(optional = false) @JoinColumn(name = "author_id")
    private User author;
}
# 2. generate: writes db/migration/<version>/ and updates model-snapshot.json
./gradlew wavebaseGenerateMigration

# 3. commit the entity, the Migration and the snapshot together
git add -A && git commit -m "Add Post.summary"

# 4. CI: `check` fails if someone forgets step 2
./gradlew check
# 2. generate: writes db/migration/<version>/ and updates model-snapshot.json
mvn compile wavebase:generate-migration

# 3. commit the entity, the Migration and the snapshot together
git add -A && git commit -m "Add Post.summary"

# 4. CI: `mvn verify` fails if someone forgets step 2
mvn verify

Automatic apply

WaveBase runs the SQL itself. Good for local, test and most CI/CD environments.

./gradlew wavebaseApply
./gradlew wavebaseRevert            # last one
./gradlew wavebaseRevert --to <v>   # everything after <v>
mvn wavebase:apply
mvn wavebase:revert                     # last one
mvn wavebase:revert -Dwavebase.to=<v>  # everything after <v>

DBA applies by hand

A DBA runs the generated SQL file; WaveBase only records it. Drift check confirms the result.

# DBA runs <v>/postgresql.up.sql
./gradlew wavebaseMarkApplied --version <v>
./gradlew wavebaseVerifySchema
# DBA runs <v>/postgresql.up.sql
mvn wavebase:mark-applied -Dwavebase.version=<v>
mvn wavebase:verify-schema

Onboard an existing database

Reverse Scaffold writes one entity class per table. It runs once: after that, your entities are the source of truth.

./gradlew wavebaseReverseScaffold --package com.acme.entity
# review and commit the generated classes, then create the baseline:
./gradlew wavebaseGenerateMigration
./gradlew wavebaseMarkApplied --version <v>   # tables already exist
mvn wavebase:reverse-scaffold -Dwavebase.package=com.acme.entity
# review and commit the generated classes, then create the baseline:
mvn compile wavebase:generate-migration
mvn wavebase:mark-applied -Dwavebase.version=<v>   # tables already exist
Renames are drop + add. v1 detects created/dropped tables, columns and foreign keys. A renamed column shows up as a drop followed by an add, which loses data. Always read the generated SQL before applying.

Command reference

The Gradle tasks (group wavebase) and the Maven goals behave identically. The short wavebase: prefix works when the plugin is declared in your pom.xml; otherwise use the full form io.github.agallina:wavebase-maven-plugin:0.1.0-SNAPSHOT:<goal>.

GradleMavenDatabaseWhat it does
wavebaseGenerateMigrationwavebase:generate-migrationnoDiffs entities against the snapshot, writes a new Migration and updates the snapshot. No-op if nothing changed.
wavebaseVerifyMigrationswavebase:verify-migrationsnoFails if entities changed without a Migration. Wired into check / verify. Disable with wavebase.verify.enabled=false.
wavebaseApplywavebase:applywritesRuns <dialect>.up.sql of every pending Migration, in order.
wavebaseRevert [--to v]wavebase:revert [-Dwavebase.to=v]writesReverts the last Migration, or every one after v. Fails if one is irreversible.
wavebaseMarkApplied --version vwavebase:mark-applied -Dwavebase.version=vwritesRecords v as applied, without running SQL.
wavebaseMarkReverted --version vwavebase:mark-reverted -Dwavebase.version=vwritesRemoves v from the history table, without running SQL.
wavebaseVerifySchemawavebase:verify-schemareadsCompares the live schema with the snapshot and fails on drift. Never auto-fixes.
wavebaseReverseScaffold --package pwavebase:reverse-scaffold -Dwavebase.package=preadsGenerates entity classes from an existing database.

Configuration

Gradle wavebase { }Maven -D propertyDefault
jdbcUrlwavebase.jdbcUrlrequired for database tasks
jdbcUsername / jdbcPasswordwavebase.jdbcUsername / wavebase.jdbcPasswordempty
dialectwavebase.dialectPOSTGRESQL (also MYSQL, H2)
migrationsDirectorywavebase.migrationsDirectorysrc/main/resources/db/migration
snapshotFilewavebase.snapshotFilesrc/main/resources/db/model-snapshot.json
reverseScaffoldOutputDirectorywavebase.outputDirectorysrc/main/java
verifyEnabled (or -Pwavebase.verify.enabled)wavebase.verify.enabledtrue

In Maven, put these properties in your pom's <properties> block (as in the Quickstart) and override any of them with -D on the command line.

What a Migration looks like on disk

src/main/resources/db/
├── model-snapshot.json
└── migration/
    └── 20261010143005123001/
        ├── postgresql.up.sql   postgresql.down.sql
        ├── mysql.up.sql        mysql.down.sql
        └── h2.up.sql           h2.down.sql

Using WaveBase with an AI coding agent

Point your agent at llms.txt: a single plain-text file with the concepts, every command, the defaults and the rules below. Or paste this into your project's AGENTS.md / CLAUDE.md, in the version for your build tool:

## Database schema (WaveBase)
Full reference: https://wavebase-project.github.io/llms.txt

- Entities under src/main/java are the source of truth for the schema.
- After changing any @Entity, run `./gradlew wavebaseGenerateMigration` and commit
  the new db/migration/<version>/ folder and db/model-snapshot.json together
  with the entity change.
- Never hand-edit model-snapshot.json or existing Migration folders.
- Read the generated *.up.sql / *.down.sql. Call out DROP TABLE / DROP COLUMN.
- Do not run wavebaseApply, wavebaseRevert, wavebaseMarkApplied or wavebaseMarkReverted
  against shared or production databases without approval.
- If `./gradlew check` fails on wavebaseVerifyMigrations, generate the missing Migration;
  do not disable the check.
- If wavebaseVerifySchema reports drift, report it; do not edit the snapshot.
## Database schema (WaveBase)
Full reference: https://wavebase-project.github.io/llms.txt

- Entities under src/main/java are the source of truth for the schema.
- After changing any @Entity, run `mvn compile wavebase:generate-migration` and commit
  the new db/migration/<version>/ folder and db/model-snapshot.json together
  with the entity change.
- Never hand-edit model-snapshot.json or existing Migration folders.
- Read the generated *.up.sql / *.down.sql. Call out DROP TABLE / DROP COLUMN.
- Do not run wavebase:apply, wavebase:revert, wavebase:mark-applied or wavebase:mark-reverted
  against shared or production databases without approval.
- If `mvn verify` fails on wavebase:verify-migrations, generate the missing Migration;
  do not disable the check.
- If wavebase:verify-schema reports drift, report it; do not edit the snapshot.
  • Deterministic, file-based output. Everything an agent produces is a plain file in the repo, reviewable in a pull request.
  • Safe by default. Generating and verifying never touch the database; only explicitly-named tasks do.
  • Loud failures. Missing Migrations and drift fail the build with a message naming the cause, so agents can self-correct.

Scope and status

Supported now

Java 17+, Hibernate 6 / Jakarta Persistence. PostgreSQL, MySQL, H2. Tables, columns, foreign keys. Gradle and Maven.

Not yet

Rename detection, indexes and unique constraints as separate changes, a standalone CLI, published artifacts.

Out of scope

NoSQL databases (planned as a separate project). Continuous sync from the database back to entities.