What Morphium Offers

quarkus-morphium ships a lightweight, MongoDB-backed schema/data migration mechanism. Annotate a class @MorphiumChangeUnit, mark one method @Execution, and the extension discovers it at build time and (optionally) runs it automatically at startup — recording success in a changelog collection and coordinating multiple application instances via a distributed lock, so a rolling deployment never runs the same migration twice concurrently.

The Challenge

Schema/data migrations for a document database still need the same guarantees relational migration tools provide: run exactly once, run in a defined order, survive a crash mid-run without corrupting state, and never run twice concurrently when multiple application instances start at the same time (a rolling Kubernetes deployment, for example).

Morphium Features Used

@MorphiumChangeUnit Class-level annotation marking a migration. Requires a unique, non-blank id() and an order() used to sort migrations before running them (zero-pad for readability once you have more than nine). Import: de.caluga.morphium.quarkus.migration.MorphiumChangeUnit MongoDBAtlasCosmosDB @Execution Method-level annotation, exactly one required per change-unit class. The method takes either no arguments or a single Morphium parameter. Must be idempotent: the changelog is only written after this method returns, so a crash between the side effect and the changelog write re-runs it. MongoDBAtlasCosmosDB @RollbackExecution Optional method-level annotation, invoked automatically if @Execution throws. Its own failure is attached as a suppressed exception on the original migration failure rather than swallowed. MongoDBAtlasCosmosDB Distributed Lock A single lock document (morphiumMigrationLock collection by default) with an owner and an expiry, acquired atomically via findAndModify. Multiple application instances starting concurrently coordinate through this lock instead of racing to run the same migration. MongoDBAtlasCosmosDB Changelog Every migration attempt is recorded in a changelog collection (morphiumChangeLog by default) with its state: EXECUTED, FAILED, or ROLLED_BACK. Already-executed migrations are skipped on subsequent runs. MongoDBAtlasCosmosDB

Prerequisites & Key Concepts

  • Automatic discovery, explicit execution. The Quarkus extension's build-time processor scans for @MorphiumChangeUnit classes via ClassGraph/Jandex and hands the list to the runner — no manual registration needed. Whether they actually run at startup is controlled separately by quarkus.morphium.migration.migrate-at-start (default false).
  • Idempotency is your responsibility. The runner guarantees a migration's @Execution method is not re-invoked once its changelog entry is recorded as EXECUTED — but the method body itself must be safe to run more than once in case of a crash between the side effect and that changelog write.
  • Lock TTL, not a permanent hold. The distributed lock has a configurable TTL (lock-ttl-seconds, default 60) and is renewed while a migration runs; if a process crashes while holding it, the lock simply expires rather than deadlocking every future deployment.
  • This demo bypasses migrate-at-start. That property only triggers a run once, automatically, at application boot. To let you trigger a run on demand from this page, the demo constructs its own MorphiumMigrationRunner and calls execute(...) directly — exactly what the extension's own startup code does internally.

The Migration

AddArchivedFlagMigration.java Java
import de.caluga.morphium.quarkus.migration.Execution;
import de.caluga.morphium.quarkus.migration.MorphiumChangeUnit;
import de.caluga.morphium.quarkus.migration.RollbackExecution;

@MorphiumChangeUnit(id = "001-add-archived-flag", order = "001", author = "showcase")1
public class AddArchivedFlagMigration {

    @Execution2
    public void execute(Morphium morphium) {
        var query = morphium.createQueryFor(MigrationNote.class)
            .f(MigrationNote.Fields.archived).eq(null);
        query.set(MigrationNote.Fields.archived, false, false, false, null);3
    }

    @RollbackExecution4
    public void rollback(Morphium morphium) { /* ... */ }
}
1 A zero-padded, unique id and order; the author field is purely informational.
2 Takes the live Morphium instance as its single parameter.
3 Only documents still missing the field are matched — running this twice is a safe no-op.
4 Invoked automatically if execute() throws.

Related Documentation