What Morphium Offers

Jakarta Data 1.0 support: define a @Repository interface with query methods, and the Quarkus extension generates the implementation at build time. Method-name derivation, @Find/@By annotations, JDQL queries and full MorphiumRepository escape hatch for when you need the raw Morphium API.

The Challenge

Most MongoDB ODMs do not support Jakarta Data. Developers who want declarative repositories with MongoDB are usually stuck writing manual implementations or switching to a JPA-like abstraction that doesn't fit document databases.

Jakarta Data Features

@Repository Marks an interface as a Jakarta Data repository. The quarkus-morphium extension generates the implementation at build time. Extends BasicRepository or CrudRepository for CRUD methods. Import: jakarta.data.repository.Repository MongoDBAtlasCosmosDB CrudRepository Provides save(), findById(), delete(), findAll(), insert(), insertAll() etc. Extends BasicRepository with insert operations. Import: jakarta.data.repository.CrudRepository MongoDBAtlasCosmosDB MorphiumRepository Extends CrudRepository with Morphium-specific operations: distinct(), morphium(), query(). The escape hatch for features beyond Jakarta Data 1.0 without injecting Morphium separately. Import: de.caluga.morphium.quarkus.data.MorphiumRepository MongoDBAtlasCosmosDB Query Derivation Method names like findByDepartment(), countByStatus(), existsByEmail() are parsed at build time into Morphium queries. Supports Equals, GreaterThan, LessThan, Between, Like, True, False, In, Not, and more. MongoDBAtlasCosmosDB Pagination Pass PageRequest and Order parameters to get paginated results back as Page<T> with totalElements, totalPages, hasNext etc. Import: jakarta.data.page.PageRequest, jakarta.data.page.Page MongoDBAtlasCosmosDB @Find / @By / @OrderBy Fine-grained query control. @By maps parameters to specific fields (supports dot notation for embedded fields). @OrderBy specifies static sort order. @Find marks the method as a query. Import: jakarta.data.repository.Find, By, OrderBy MongoDBAtlasCosmosDB @Query (JDQL) Jakarta Data Query Language — SQL-like syntax for complex queries. WHERE, AND, OR, LIKE, ORDER BY, BETWEEN. Parameters with :name syntax. Import: jakarta.data.repository.Query MongoDBAtlasCosmosDB @StaticMetamodel Generated at build time. Provides type-safe Sort attributes like Employee_.salary.desc() for use with Order.by(). Import: jakarta.data.metamodel.StaticMetamodel MongoDBAtlasCosmosDB

Morphium API vs Jakarta Data — Side-by-Side

CRUD Operations

OperationMorphium (imperative)Jakarta Data (declarative)
Find all morphium.createQueryFor(Product.class).asList() repository.findAll().toList()
Find by ID morphium.findById(Product.class, id) repository.findById(id)
Save (upsert) morphium.store(product) repository.save(product)
Insert (fail if exists) morphium.insert(product) repository.insert(product)
Delete morphium.delete(product) repository.delete(product)
Count morphium.createQueryFor(Product.class).countAll() repository.countByPriceGreaterThanEqual(0)

Query Derivation

Repository Interface Java
@Repository
public interface EmployeeRepository
        extends BasicRepository<Employee, MorphiumId> {

    // Equality: WHERE department = ?
    List<Employee> findByDepartment(String dept);

    // Greater Than: WHERE salary > ?
    List<Employee> findBySalaryGreaterThan(double min);

    // Between: WHERE salary >= ? AND salary <= ?
    List<Employee> findBySalaryBetween(double min, double max);

    // Boolean: WHERE active = true
    List<Employee> findByActiveTrue();

    // Count: COUNT WHERE department = ?
    long countByDepartment(String dept);

    // Exists: EXISTS WHERE email = ?
    boolean existsByEmail(String email);
}

Each method name is parsed at build time into a Morphium query. The equivalent imperative code would be:

Equivalent Morphium API Java
// findByDepartment("Engineering")
morphium.createQueryFor(Employee.class)
    .f(Fields.department).eq("Engineering")
    .asList();

// findBySalaryGreaterThan(90000)
morphium.createQueryFor(Employee.class)
    .f(Fields.salary).gt(90000)
    .asList();

// countByDepartment("Engineering")
morphium.createQueryFor(Employee.class)
    .f(Fields.department).eq("Engineering")
    .countAll();

Pagination & Sorting

MorphiumJakarta Data
query.f(Fields.department).eq(dept)
    .sort(Map.of(Fields.salary, -1))
    .skip((page - 1) * size)
    .limit(size)
    .asList();
repository.findByDepartment(dept,
    PageRequest.ofPage(page, size, true),
    Order.by(Sort.desc("salary")));

Jakarta Data returns a Page<T> with totalElements(), totalPages(), hasNext() — all in one call. With Morphium, you need a separate countAll() call.

@Find / @By / @OrderBy

Fine-Grained Queries Java
// Embedded field (dot notation) + sort + limit
@Find
@OrderBy(value = "salary", descending = true)
List<Employee> topEarnersInDepartment(
    @By("department") String dept,
    @By("active") boolean active,
    Limit limit);

// Equivalent Morphium API:
morphium.createQueryFor(Employee.class)
    .f(Fields.department).eq(dept)
    .f(Fields.active).eq(true)
    .sort(Map.of(Fields.salary, -1))
    .limit(2)
    .asList();

@Query / JDQL

JDQL (Jakarta Data Query Language) Java
// Complex query with multiple conditions + sort
@Query("WHERE department = :dept"
     + " AND salary >= :minSalary"
     + " AND active = true"
     + " ORDER BY salary DESC")
List<Employee> topActiveEarners(
    @Param("dept") String dept,
    @Param("minSalary") double minSalary);

// LIKE pattern matching (replaces Morphium regex)
@Query("WHERE name LIKE :pattern ORDER BY price ASC")
List<Product> searchByNameLike(
    @Param("pattern") String pattern);

@StaticMetamodel

Generated Metamodel Java
// Generated at Quarkus build time (not in source code!)
@StaticMetamodel(Employee.class)
public class Employee_ {
    public static volatile Attribute<Employee> firstName;
    public static volatile SortableAttribute<Employee> salary;
    public static volatile TextAttribute<Employee> email;
    // ... all fields of the entity
}

// Usage: type-safe sort (no magic strings!)
Order.by(Employee_.salary.desc());

Morphium ORM Features — Transparent Through Repositories

Since generated repositories delegate to morphium.store(), morphium.findById() etc. internally, all Morphium ORM features work transparently through Jakarta Data repositories:

  • @Version (Optimistic Locking)repository.save(entity) checks and increments the version automatically. A VersionMismatchException is thrown on concurrent modification, just like with morphium.store().
  • @CreationTime / @LastChange — Timestamps are set automatically on every save() or insert() call through the repository.
  • @PreStore / @PostStore / @PostLoad — All lifecycle callbacks fire normally when entities are loaded or stored through repositories.
  • @Cache / @WriteBuffer — Morphium's read cache and write buffer are active for all repository queries.
  • @Reference — Lazy and eager references are resolved automatically when entities are loaded via findById(), findAll() or query methods.
  • @Index — Indexes are created on startup, independent of how you access the data.

MorphiumRepository — The Escape Hatch

MorphiumRepository<T, K> extends CrudRepository with three Morphium-specific methods for features beyond Jakarta Data 1.0:

  • distinct(fieldName) — returns unique values for a field across all documents
  • morphium() — direct access to the Morphium instance for aggregation, atomic updates, etc.
  • query() — creates a typed Morphium Query for complex conditions

This means you can use repository.morphium().inc(entity, "field", 1) instead of injecting Morphium separately. All standard Jakarta Data features still work exactly the same.

Requires Direct Morphium API

The following features require MorphiumRepository.morphium() or @Inject Morphium:

  • Atomic updates ($inc, $set, $push, $pull) — field-level updates without loading the entity
  • Aggregation pipelines — $group, $lookup, $unwind, $bucket
  • Change streams / Messaging — real-time event processing

When to Use Which Approach?

Use CaseRecommendation
Standard CRUD Jakarta Data — less boilerplate, declarative, type-safe
Simple queries (equality, range, boolean) Jakarta Data — query derivation from method names
Complex queries (multi-condition, sort, LIKE) Jakarta Data @Query/JDQL — SQL-like, readable
Pagination Jakarta Data — Page<T> with total count in one call
Atomic field updates ($inc, $set) Morphium API — field-level updates without loading the entity
Aggregation pipelines Morphium API — $group, $lookup, $unwind etc.
@Version, @CreationTime, @PreStore, @Cache Both! — work transparently through repository calls
Distinct queries MorphiumRepositoryrepository.distinct("field")
Mixed: CRUD + advanced features MorphiumRepository — Jakarta Data + morphium() / query() escape hatch

How It Works Under the Hood

Build-Time Code Generation (Gizmo) text
1. Jandex scans for @Repository interfaces at build time
2. Quarkus build step extracts entity type, ID type from generics
3. Custom method names are parsed: findByXxxAndYyyGreaterThan
4. Gizmo generates implementation class:
   - extends AbstractMorphiumRepository<T, K>
   - implements YourRepository interface
   - delegates to Morphium API at runtime
5. CDI registers the generated bean as @ApplicationScoped
6. @StaticMetamodel classes generated for all @Entity types

Result: Zero runtime reflection, GraalVM native compatible!

Related Documentation