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.
@Entity classes are the source of truth.up.sql / down.sql per dialect.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.jsonMigration
A versioned folder with plain SQL per dialect. A missing down.sql means it is irreversible on that dialect.
Schema History Table
Lives in the database and records which Migrations are applied there. Created on first use.
wavebase_schema_historyReverse 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.
-
Clone WaveBase next to your project
git clone https://github.com/WaveBase-Project/WaveBase.git ../WaveBase
-
Include the build and apply the plugin
settings.gradle.ktspluginManagement { includeBuild("../WaveBase") }
build.gradle.ktsimport 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 }
-
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/... -
Review, commit, apply
git add src/main/resources/db git commit -m "Initial schema" ./gradlew wavebaseApply
-
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./gradlewscript 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
-
Add WaveBase to your
pom.xmlTwo 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> -
Make sure the JDBC driver is a dependency
Most projects already have it. If not, add the one for your database:
Database wavebase.dialectDriver dependency PostgreSQL POSTGRESQLorg.postgresql:postgresql:42.7.3MySQL MYSQLcom.mysql:mysql-connector-j:8.4.0H2 H2com.h2database:h2:2.2.224Show a complete
pom.xmlto start frompom.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> -
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
-
Generate your first Migration
Run
compilein 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/... -
Review, commit, apply
git add src/main/resources/db git commit -m "Initial schema" mvn wavebase:applyFrom now on,
mvn verifyandmvn installfail 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-schemaOnboard 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
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>.
| Gradle | Maven | Database | What it does |
|---|---|---|---|
wavebaseGenerateMigration | wavebase:generate-migration | no | Diffs entities against the snapshot, writes a new Migration and updates the snapshot. No-op if nothing changed. |
wavebaseVerifyMigrations | wavebase:verify-migrations | no | Fails if entities changed without a Migration. Wired into check / verify. Disable with wavebase.verify.enabled=false. |
wavebaseApply | wavebase:apply | writes | Runs <dialect>.up.sql of every pending Migration, in order. |
wavebaseRevert [--to v] | wavebase:revert [-Dwavebase.to=v] | writes | Reverts the last Migration, or every one after v. Fails if one is irreversible. |
wavebaseMarkApplied --version v | wavebase:mark-applied -Dwavebase.version=v | writes | Records v as applied, without running SQL. |
wavebaseMarkReverted --version v | wavebase:mark-reverted -Dwavebase.version=v | writes | Removes v from the history table, without running SQL. |
wavebaseVerifySchema | wavebase:verify-schema | reads | Compares the live schema with the snapshot and fails on drift. Never auto-fixes. |
wavebaseReverseScaffold --package p | wavebase:reverse-scaffold -Dwavebase.package=p | reads | Generates entity classes from an existing database. |
Configuration
Gradle wavebase { } | Maven -D property | Default |
|---|---|---|
jdbcUrl | wavebase.jdbcUrl | required for database tasks |
jdbcUsername / jdbcPassword | wavebase.jdbcUsername / wavebase.jdbcPassword | empty |
dialect | wavebase.dialect | POSTGRESQL (also MYSQL, H2) |
migrationsDirectory | wavebase.migrationsDirectory | src/main/resources/db/migration |
snapshotFile | wavebase.snapshotFile | src/main/resources/db/model-snapshot.json |
reverseScaffoldOutputDirectory | wavebase.outputDirectory | src/main/java |
verifyEnabled (or -Pwavebase.verify.enabled) | wavebase.verify.enabled | true |
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.sqlUsing 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.
Community
WaveBase is open source and shaped in the open. Design decisions are tracked as GitHub issues, so every behaviour on this page has a discussion behind it.
Questions and feedback
Until the source opens, open an issue on the site repository. Include your entities, dialect and what you expected.
Contribute code
The source repository opens with the first release. Follow the organization to know when pull requests are open.
Improve this site
This page and llms.txt live in a public repository. Fixes are welcome.