caluga - java blog

Morphium 6.2.1 β€” A Bugfix Release That Packs a Punch

Morphium 6.2.1 β€” A Bugfix Release That Packs a Punch

Morphium 6.2.1 is out. Officially a bugfix release, but anyone who takes a look at the changelog will quickly notice: there's serious work in here. From the ByteBuddy migration to PoppyDB stability improvements and a new Stream API β€” this is no mere patch release.

ByteBuddy Instead of spring-cglib

This was long overdue: the dependency on spring-cglib for lazy-loading proxies is gone. The replacement is ByteBuddy 1.15.11 β€” which also means spring-core is completely removed from the dependency tree.

What this means in practice:

  • No more --add-opens β€” the annoying JVM flag for reflection access is no longer needed
  • Native Java 21 support β€” ByteBuddy handles this without workarounds
  • Proxy class caching β€” prevents Metaspace leaks when proxies are generated repeatedly

For projects that don't otherwise need spring-core, the dependency list shrinks noticeably.

PoppyDB: Stability Overhaul

PoppyDB, the embedded in-memory MongoDB-compatible server, got a lot of attention in this release. Some of the fixed issues were genuinely nasty edge cases:

  • BSON size limits β€” documents exceeding the 16MB limit were handled incorrectly, leading to corrupted responses
  • Aggressive idle timeouts β€” Change Streams were being killed because PoppyDB marked connections as idle too eagerly
  • Stale primary state after elections β€” after a replica set election, PoppyDB could get stuck in the wrong primary state
  • Connection-killing parse errors β€” certain BSON parse errors would abort the entire connection instead of just rejecting the request

Anyone using PoppyDB for testing will benefit directly β€” especially with Change Streams and longer-running test suites.

Wire Protocol Hardening

The wire protocol got some defensive improvements as well:

  • EOF handling β€” unexpected connection drops could previously cause infinite loops; this is now handled cleanly
  • Closed connections β€” are no longer returned to the connection pool, preventing cascading errors

This sounds mundane, but in production environments with flaky network infrastructure (which exist more often than you'd think), this is the difference between a "brief hiccup" and "service hangs until restart".

Query.stream() β€” Lazy Cursor-backed Streams

New in this release: Query.stream() returns a Stream<T> that lazily iterates over a MongoDB cursor.

morphium.createQueryFor(MyEntity.class)
    .field("status").eq("active")
    .stream()
    .filter(e -> e.getValue() > 100)
    .forEach(e -> process(e));

The key difference from asList(): results aren't all loaded into the heap at once. For large collections, this is a genuine game-changer for memory usage. The cursor is closed automatically at the end of the stream.

Read-your-writes Consistency

After a transaction commit, reads are routed to PRIMARY for a configurable time window. This sounds like a minor detail, but it prevents an entire class of hard-to-debug race conditions where you write data and then immediately "can't find it" because the read landed on a secondary with replication lag.

MongoField.not() β€” Breaking Change

MongoField.not() now returns MongoField<T> instead of Query<T>. This was necessary to produce the correct BSON structure for all operators β€” previously the behavior was wrong for certain operator combinations.

What changes: Anyone using .not() in a fluent chain and then calling Query-specific methods will need to adjust the chain. In most cases this is a minimal refactoring effort.

Contributor Credits

A big thank you to Heiko Kopp (@Bardioc1877) for his contributions to this release.

<dependency>
    <groupId>de.caluga</groupId>
    <artifactId>morphium</artifactId>
    <version>6.2.1</version>
</dependency>