What Morphium Offers

Two independent Morphium-core capabilities beyond ORM basics: field-level encryption, which transparently encrypts individual entity fields before they hit MongoDB and decrypts them on load, and a JSR-107 (JCache)-backed cache implementation as a drop-in alternative to Morphium's default in-memory cache.

The Challenge

Sensitive fields (secrets, PII) need encryption at rest without every read/write path in the application having to encrypt/decrypt by hand. Separately, applications that already standardize on a JSR-107 cache provider (EHCache, Hazelcast, ...) for the rest of their stack shouldn't need a second, incompatible caching mechanism just for Morphium.

Morphium Features Used

@Encrypted Field-level annotation. Attributes: provider() (default AESEncryptionProvider.class) and keyName() (defaults to the owning class's fully-qualified name). ObjectMapperImpl encrypts on serialize, decrypts on deserialize -- entirely transparent to application code. Import: de.caluga.morphium.annotations.encryption.Encrypted MongoDBAtlasCosmosDB EncryptionKeyProvider Holds the encryption/decryption keys by name. Reach it via morphium.getEncryptionKeyProvider() and call setEncryptionKey(name, bytes)/setDecryptionKey(name, bytes) before the first store()/query touching an @Encrypted field. Built-in impls also support reading keys from Properties or MongoDB itself. MongoDBAtlasCosmosDB @Cache Type-level annotation enabling query-result caching for an entity. Same annotation regardless of which MorphiumCache implementation backs it -- default in-memory, or JSR-107. Attributes: timeout, maxEntries, strategy (LRU/FIFO/RANDOM), clearOnWrite, readCache. Import: de.caluga.morphium.annotations.caching.Cache MongoDBAtlasCosmosDB MorphiumCacheJCacheImpl A MorphiumCache implementation backed by any javax.cache.spi.CachingProvider (JSR-107). Falls back to Morphium's own minimal in-memory reference implementation if no external JCache provider (EHCache, Hazelcast, ...) is on the classpath -- no extra dependency required for a self-contained demo. Import: de.caluga.morphium.cache.MorphiumCacheJCacheImpl MongoDBAtlasCosmosDB

Prerequisites & Key Concepts

  • Encryption keys must exist before first use. Register them via morphium.getEncryptionKeyProvider() at application startup (e.g. a CDI @PostConstruct) — there is no automatic key generation.
  • A normal query can never prove encryption. morphium.createQueryFor(Entity.class).get() always returns the transparently-decrypted value, by design. To see the ciphertext, you must read the raw MongoDB document via the low-level driver, bypassing the object mapper entirely.
  • The cache implementation is chosen per Morphium instance, at configuration timecacheSettings().setCache(...) must be set before the instance connects, and cannot be swapped afterwards. Because the CDI-injected Morphium already uses the default cache for every other feature in this showcase, the JCache demo below runs against a second, independent Morphium instance rather than reconfiguring the shared one.
  • Cache activation is annotation-driven either way. Switching cache implementations changes how caching works under the hood, not which entities are cached — that remains controlled by @Cache on the entity, same as the Product Catalog's non-JCache caching demo.

Field-Level Encryption: Entity Source Code

SecureNote.java Java
import de.caluga.morphium.annotations.encryption.Encrypted;
import de.caluga.morphium.encryption.AESEncryptionProvider;

@Entity(collectionName = "secure_notes")
public class SecureNote {

    @Id
    private MorphiumId id;

    private String title;      // plaintext

    @Encrypted(provider = AESEncryptionProvider.class, keyName = "secure-note-key")1
    private String secretContent;
}
1 Everything else about this field is ordinary Java — no manual encrypt/decrypt calls anywhere in application code.
Registering keys and proving ciphertext Java
@PostConstruct
void ensureKeys() {1
    morphium.getEncryptionKeyProvider().setEncryptionKey(KEY_NAME, DEMO_KEY);
    morphium.getEncryptionKeyProvider().setDecryptionKey(KEY_NAME, DEMO_KEY);
}

// Bypasses the object mapper entirely -- returns the document exactly as MongoDB stores it
FindCommand fc = new FindCommand(morphium.getDriver().getPrimaryConnection(null))2
    .setDb(morphium.getDatabase())
    .setColl(morphium.getMapper().getCollectionName(SecureNote.class))
    .setFilter(Map.of("_id", note.getId()));
List<Map<String,Object>> raw = fc.execute();
// raw.get(0).get("secretContent") is a byte[] -- ciphertext, not the plaintext string
1 Must run before the first store()/query touching an @Encrypted field.
2 This is the only way to see the raw ciphertext -- a normal query always decrypts transparently.

JCache: Standalone Instance Setup

Configuring a second Morphium instance with JCache Java
MorphiumConfig cfg = new MorphiumConfig();
cfg.connectionSettings().setDatabase("showcase-jcache-demo");1
cfg.driverSettings().setDriverName(InMemoryDriver.driverName);
cfg.cacheSettings().setCache(new MorphiumCacheJCacheImpl());2
Morphium jcacheMorphium = new Morphium(cfg);3
1 A separate database name keeps this instance's data isolated from the rest of the showcase.
2 Must be set on the config before constructing the instance — if left unset, Morphium defaults to its own in-memory cache during initialization.
3 The constructor connects synchronously; no separate .connect() call needed.

Related Documentation