Releases: FlameyosSnowy/Universal
Universal - v7.1.6
Fixes
- Eliminate getDeclaredConstructor() reflection. (Making Universal fully graalvm-safe)
- Use
Typesinstead of string matching for safer processing. - Make TypeResolver a more trusted source of truth and skipping many checks (which is the intended behavior)
Improvements
- Improve the performance by reusing computations.
Universal - v7.1.5
Improved
- Improved performance of SQLDatabaseParameters construction heavily
- Made the cache configuration of AbstractRelationshipHandler configurable
- Updated to Java 25
Fixed
- Fixed infinite resizing in AbstractRelationshipHandler; replaced with LRU eviction
- Fixed loadFromDatabaseBatch not adding to available caches
- Fixed
isEnabledandsetEnablednot being recognized by the compile time processor when working with getters and setters of boolean fields (The old workaround was to dogetIsEnabledandsetIsEnabledor not addisat all) - Fixed record-style getters and setters not being recognized by the compile time processor
- Fixed mongodb select queries sometimes not working when working with relationships
Full Changelog: 7.1.1...7.1.5
Universal - v7.1.1
GraalVM Native Image Support
Replaced Class.forName bootstrapping with ServiceLoader, making Universal fully compatible with GraalVM native image compilation. All generated repository factory classes now implement GeneratedRepositoryFactory and are discovered at startup via ServiceLoader without requiring reflection configuration.
JSON / PostgreSQL Fix
Fixed incorrect SQL parameter placeholder for JSON fields when targeting PostgreSQL. Parameters are now emitted as ?::jsonb instead of bare ?, resolving type cast errors on insert and update operations.
JSON on SQL
Fixed an issue where JSON fields were not being handled correctly during SQL query execution, causing serialization or persistence failures for @JsonField-annotated columns.
@JsonVersioned and @JsonIgnoreNulls
Both annotations were previously accepted by the processor but had no effect at runtime, this behavior has been corrected.
@JsonVersioned now participates in version tracking and @JsonIgnoreNulls has been removed
@Named Annotation
Fixed a bug where the @Named annotation was not being applied correctly in certain contexts, causing the wrong column name to be used in generated SQL.
DefaultSession Batch Execution Logic
Fixed an inverted boolean check in DefaultSession that caused batch buffering to behave the opposite of its intended configuration - modifications were being executed immediately when buffering was enabled, and batched when it was disabled. This is now corrected.
Full Changelog: 7.1.0...7.1.1
Universal - v7.1.0
Session API overhaul
more like a fix, lol
FileSession and NetworkSession now support all SessionOptions
Both microservice session implementations previously ignored session options
entirely. They now honour the full set supported by DefaultSession:
NO_CACHE- bypasses the in-memory cache on reads and suppresses
write-back. EveryfindByIdandfindAllByIdcall goes straight to storage.BUFFERED_WRITE- defers all writes to apendingOperationsqueue
and flushes them only whencommit()is explicitly called. Auto-flush is
suppressed while this option is active.LOG_OPERATIONS- emits anINFOlog entry viaLoggingfor every
INSERT,UPDATE, andDELETE, including the entity value.CASCADE- propagates writes through the relationship graph via
DefaultGraphCoordinator, with a non-cascading inner view passed to the
coordinator to prevent infinite recursion.
Rollback callbacks now correctly restore previous cache state for updates and
deletes in both sessions, matching the contract DefaultSession has always had.
New bulk methods across the entire session stack
DatabaseSession, DefaultSession, FileSession, and NetworkSession all
gained the following methods. The interface provides default implementations
that delegate to the single-entity methods, so any custom DatabaseSession
implementation inherits them automatically at no cost.
| Method | Description |
|---|---|
insertAll(Iterable<T>) |
Insert a collection of entities |
updateAll(Iterable<T>) |
Update a collection of entities |
deleteAll(Iterable<T>) |
Delete a collection of entities |
deleteAllById(Iterable<ID>) |
Delete by primary key, resolving through cache |
findAllById(Collection<ID>) |
Batch read; cache hits are short-circuited, misses are fetched in one call |
*Async variants |
Every bulk method has a CompletableFuture-based async counterpart |
DefaultSession.findAllById goes a step further: it separates cache hits from
misses and issues a single repository.findAllById(missingIds) call for the
remainder, eliminating per-ID round-trips.
Session introspection API
Three new methods are available on DatabaseSession and all implementations:
void setAutoFlush(boolean autoFlush) // enable / disable threshold-based flushing
int getBatchSize() // returns -1 for non-batching sessions
int getPendingOperationCount() // staged but not yet committed operationsDefaultSession returns -1 for getBatchSize() and reflects only its
pendingOperations list in getPendingOperationCount() (non-zero only under
BUFFERED_WRITE). FileSession and NetworkSession sum all four staging
collections (insert map, update map, delete list/set, pending ops).
Relationship handler overhaul
AbstractRelationshipHandler has been significantly upgraded. The previous
implementation is still available as a reference but the active implementation
now includes:
- Tiered L1 + L2 cache - a
ThreadLocal<WeakReference<Map>>L1 cache sits
in front of the sharedConcurrentHashMapL2 cache. Hot-path reads never
touch shared memory. L1 hits are promoted automatically. - Query result cache - identical
WHERE field = idqueries are deduplicated
so repeatedhandleOneToManyRelationshipcalls for the same parent do not
re-query the database. - Adapter cache -
resolveAdapternow caches results in a per-handler
ConcurrentHashMapkeyed onfieldName:entityClass, eliminating repeated
RepositoryRegistrylookups. - Cache key pooling - a 1 024-slot intern pool eliminates
String
allocation on cache key construction forInteger/LongIDs (the common
case). MANY_TO_ONEbatch prefetch - the previousprefetch()silently ignored
MANY_TO_ONEfields.batchLoadManyToOneis now implemented and included
in all prefetch paths.- Parallel batch loading - when
setParallelPrefetchEnabled(true)is set,
the three batch-load types (ONE_TO_ONE,ONE_TO_MANY,MANY_TO_ONE) run
concurrently via a daemonExecutorServicewith a 30-second timeout. - Deep / recursive prefetch -
prefetchDeep(Collection, String...)accepts
dot-notation field paths (e.g."faction.warp","faction.members") and
recursively prefetches relationship graphs up to a configurable depth. - Cache warming by ID -
warmCache(List<ID>, Set<String>)preloads
relationship data from IDs alone, without needing entity objects. - Auto-behaviours - three opt-in flags, all off by default:
autoWarmCache,autoDeepPrefetch(with configurable depth), and
parallelPrefetchEnabled. getRelationshipSize- returns the count of aONE_TO_MANYcollection
without loading it. Uses the cached size if the collection is already loaded,
otherwise issues aCOUNT(*)query.- Performance metrics -
getMetrics()returns aRelationshipMetrics
record exposing cache hit / miss counts, L1 vs L2 hit breakdown, per-field
query counts, cache sizes, and configuration state.resetMetrics()resets
all counters. - Proper lifecycle -
clear()now wipes the query result cache, adapter
cache, and cache key pool in addition to the relationship cache.clearAll()
also wipes the staticnameCacheshared across instances.shutdown()
gracefully terminates the parallel executor.
Array handling fixes
InsertEntityGenerator / InsertCollectionEntitiesGenerator
Array fields are now routed through mutually exclusive paths depending on
whether the database supports native array types:
- Native path (
supportsArraysNatively() == true) - the array is bound
directly viastmt.set()insideinsertEntity. - Non-native path - the array is skipped in
insertEntityand handled by
handler.insertArray()insideinsertCollectionEntities.
Previously both paths could fire for the same field.
ValueReaderGenerator
The generated readN() helper methods for array fields now emit the same
branch:
if (result.supportsArraysNatively()) {
return (T[]) result.getArray(columnName, T.class);
} else {
return result.getCollectionHandler().fetchArray(id, columnName, T.class, repositoryModel);
}Maps and collections are unaffected - they are always stored relationally.
Uniform replaces Jackson for JSON serialisation
All JSON serialisation and deserialisation has been migrated from Jackson to
Uniform, a purpose-built codec layer developed in-house.
Why the switch?
| | Jackson | Uniform |
|---|---|---|---|
| Startup cost | High (reflection scan) | Little-to-none (compile-time) |
| Allocations per parse | High| Minimal |
| Custom type support | Annotation-driven | First-class |
| SIMD | No | Yes (stage-1 SIMD, stage-2 SWAR) |
Jackson's reflection-based approach was the largest single contributor to cold
start time in serverless and microservice deployments. Uniform generates codec
classes at compile time via the annotation processor, eliminating all runtime
reflection and producing significantly smaller allocation profiles per parse.
Migration impact
No changes are required for entity classes. Annotations such as @JsonProperty
are no longer needed - field-to-column mapping is handled by the existing
@Repository / @Column metadata that the processor already understands.
If you were accessing the Jackson ObjectMapper directly (e.g. via
adapter.getObjectMapper()), that method has been changed to return a JsonAdapter
Code maintainability improvements
UnifiedFactoryGenerator split into focused generators
The original 900-line UnifiedFactoryGenerator has been refactored into ten
single-responsibility classes:
| Class | Responsibility |
|---|---|
UnifiedFactoryGenerator |
Orchestrator - delegates, owns nothing |
RepositoryModelGenerator |
Generates *_RepositoryModel_Impl |
ObjectModelGenerator |
Generates *_ObjectModel |
RelationshipLoaderGenerator |
Generates *_RelationshipLoader |
LazyProxyGenerator |
Generates lazy entity / collection proxies |
RelationshipMethodGenerator |
oneToOne / oneToMany / manyToOne methods |
CollectionLoaderMethodGenerator |
loadList / loadSet / loadMap / loadArray |
InsertEntityGenerator |
insertEntity method body |
InsertCollectionEntitiesGenerator |
insertCollectionEntities method body |
GeneratorUtils |
Stateless shared utilities |
Each generator is independently unit-testable, depends only on the types it
needs (Types, Elements, Filer, Messager), and can be extended or
replaced without touching the others.
Bug fixes
DefaultSession.insertDirect/updateDirect- cache write-back after a
successful operation was not guarded byNO_CACHE. Both now skip the
cache.put()call whenNO_CACHEis set.FileSession/NetworkSessionrollback - rollback callbacks (which
restore previous cache state for updates and deletes) were not being invoked.
Both sessions now run callbacks before clearing state, matching
DefaultSession's contract.
Universal - v7.0.0
Changelog
Major Architecture Changes
Code Generation System (Full Rewrite of Hot Paths)
- Replaced reflection-based hot paths with compile-time code generation.
- Eliminated runtime
TypeResolverscanning in performance-critical sections. - Improved validation during compilation instead of failing at runtime.
- Split monolithic files into smaller, responsibility-driven modules.
- Reduced object allocations and removed unnecessary intermediate collections.
Impact:
- Significant performance gains.
- Lower GC pressure.
- Better startup characteristics.
- Stronger compile-time guarantees.
Consistency & Read Policy System
Introduced a formal consistency contract for reads:
Consistency.STRONGConsistency.EVENTUAL- Adapter-defined default behavior
Features:
- Explicit read policies enforced at adapter level.
- Strong reads bypass caching layers.
- Eventual reads prefer cache with fallback behavior.
- Consistency abstraction decoupled from caching implementation.
- Added
@Resolvesannotation for compile-time resolver binding. - Added
countfunction for efficient counting of elements in the database, works for all SQL, MongoDB, File, Network, etc - Added
match(MUST make a text index for this to work in MongoDB), like, and, or and others in the new Query API
This enables predictable read guarantees across SQL and Mongo adapters.
Query & JSON System Overhaul
New Query API
- Fully redesigned query DSL.
Query.select()
.where("age")
.gte(18)
.where("name")
.like("A%")
.build();- Better composability.
- Improved validation logic.
- More efficient execution planning.
- New aggregation API (including but not limited to the example):
var query = Query.aggregate()
.select(
Query.field("departmentId"),
Query.field("status").countIf(eq("active")).as("activeCount"),
Query.field("status").countIf(eq("inactive")).as("inactiveCount"),
Query.field("salary").avg().as("avgSalary")
)
.groupBy("departmentId")
.having().field("id").count().gt(5)
.orderBy("avgSalary", DESC)
.build();
var results = repository.aggregate(
Query.aggregate()
.select(
Query.field("status").countIf(eq("active")).as("active_count"),
Query.field("status").countIf(eq("inactive")).as("inactive_count")
)
.build()
);
var deptStats = repository.aggregate(
Query.aggregate()
.select(
Query.field("departmentId"),
Query.field("id").count().as("emp_count")
)
.groupBy("departmentId")
.having().field("id").count().gt(10)
.orderBy("emp_count", DESC)
.build()
);New JSON API
- First-class JSON system via JsonField annotation (which means you don't need to make TypeResolvers just to serialize something in JSON) and filtering by JSON
- Powered by Jackson... for now 😉
Session API Improvements
Added:
run()runVoid()runAsync()
These provide automatic atomic session handling with consistent lifecycle management.
Improves safety around transaction-like behavior.
Kotlin Support
- Added Kotlin extension module.
- Support for
suspendfunctions. - Improved Kotlin DSL ergonomics.
- Better type inference and null-safety alignment.
- Kotlin QOL improvements.
Now Kotlin usage feels native instead of bolted on.
Performance Optimizations
- Replaced enhanced for-loops with raw loops in critical paths.
- Heavy lambda, string-building and collection memory allocation micro-optimizations.
- Removed unnecessary collection allocations.
- Eliminated debug artifacts.
- Heavy internal optimizations that compound under load.
- Micro-optimized relationship traversal
These changes focus on hot-path efficiency rather than superficial tuning.
Fixes & Stability
- Fixed microservices
TypeResolverregistration issues. - Handled no-ID entity scenarios correctly.
- Multiple bug fixes across relationship and query layers.
- Improved test coverage and reliability.
- Fixed edge-case validation issues.
- Improved handling of “no ID” edge cases
- Fixed mongodb (as a whole, lol) not working
Tooling & Platform
- Upgraded to Gradle 9.
- Updated Kotlin version.
- Version bumps and dependency updates.
- Improved build modularization.
Date/Time Improvements
- Added
MonthDaysupport. - Improved “now” processing
Universal - v6.1.6
Improvements
- Add RepositoryAdapter#findIterator and RepositoryAdapter#findStream for lazily iterating over ResultSets, Documents and File/Network data MAKE SURE TO CLOSE THOSE ITERATORS/STREAMS BECAUSE THEY CONTAIN DATABASE RESOURES
- Add SelectQuery#orderBy(List)
Bug Fixes
- Fix some memory leaks
- Fix ResultSet not being properly closed in collection resolvers
- Fix caches being created and used even when caching is not enabled.
- Other misc fixes.
Full Changelog: 6.1.5...6.1.6
6.1.5
- Fix Collections like List, Set or Queue if DatabaseImplementation#supportsArrays equals true.
Full Changelog: 6.1.4...6.1.5
Universal - v6.1.4
- Improve deepInfo debugging in QueryParseEngine
Universal - v6.1.3
Improvements
- Add new java.time classes to support, such as:
- Year
- YearMonth
- Month
- ZoneId
- TimeZone
- OffsetTime
- Improved Multimap and collection support, it now Supports:
- Queue interface
- Deque interface
- Multimaps with Queue/Deque/Set
Breaking changes
- Remove cassandra
We have decided that Cassandra is not a good fit for the relationship-heavy philosophy of Universal and have not only discontinued it's maintenance but we have removed it entirely.
It will not be a good fit for our future and not for our users, please use the direct Cassandra API (advised) or any library/object mapper that supports all you need for Cassandra support.
Universal - v6.1.2
- Fix enums not working
Full Changelog: 6.1.1...6.1.2