ZeroZ DB · The zero-impedance database

You already have a data model. Why describe it twice?

Every entity in a conventional Java application exists as at least two things — the class and the table — plus a mapping layer whose entire job is to stop the two versions of one idea from drifting apart. ZeroZ DB deletes the second version.

Artifactcom.zeroz4j:zerozdb
RuntimeJava 21 · pure Java, no native code
LicenceApache 2.0

The cost

Two schemas, one idea.

The database mismatch

Domain models in Java are translated via SQL or JPA/Hibernate to map onto relational tables — creating dual-schema maintenance for a single idea.

The mapping layer is a permanent tax

It expresses no business rule and produces no feature, and you maintain it for the life of the system. Its cost is invisible in a sprint and enormous in aggregate.

The queries are in a different language

The compiler cannot check them, the IDE cannot refactor through them, and a rename in Java leaves a string in a repository that still compiles and now returns nothing.

The performance failures are structural

N+1 selects, lazy-loading exceptions outside the session, and caches that exist entirely to undo the cost of a translation you did not want in the first place.

The solution

Your objects are the database.

ZeroZ DB stores a Java object graph directly. A root object holds your maps and lists; those hold your entities; you navigate them with field access and query them with streams. The graph lives in the heap at heap speed and is written to disk as binary. There is no schema to declare, no mapping to configure and no query language to learn — because there is nothing between your classes and the storage.

SERVER · ProspectService.java● The real API
ZeroZDb db = ZeroZDb.open(new MyRoot(), Path.of("data/mystore"));
MyRoot root = db.root();

db.write(ctx -> {                       // serialized, atomic, durable on return
    root.prospects().put(id, prospect); // a plain Java mutation
    ctx.store(root.prospects());
});

List<Prospect> hot = db.read(() ->      // concurrent, never sees torn state
    root.prospects().values().stream().filter(Prospect::isHot).toList());

If that looks like it is missing the database, that is the point. The write block is the transaction; the stream is the query.

02 · Guarantees

Everything a real database owes you.

Object-graph persistence on its own is a storage engine, not a database. ZeroZ DB is what has to be added before a system of record can sit on one.

Atomic transactions

Every write runs inside a block that either commits entirely or leaves the graph as it was. Writes are serialized against one another, so a commit is never interleaved with another commit. Rollback restores the in-memory objects from before-images taken at enlistment — which is why the API asks you to enlist an object before you change it.

Durability that actually calls fsync

Plain object-graph engines return from a commit when the operating system has accepted the bytes, not when the disk has them: the data survives a process crash but not a power cut. ZeroZ DB forces the channel after every storage write by default. The weaker, faster behaviour is still available for bulk loads, but you have to ask for it.

Concurrent readers

Readers run against the live graph without blocking one another and without ever seeing a half-applied write. The read/write lock is deliberately fair rather than throughput-optimal: the alternative starves readers under sustained write load, which is a worse failure than a slightly slower writer.

Maintained indexes and unique constraints

Register an index on a field and it is kept current as part of the commit, not by a background job that can lag. A violated unique constraint aborts the whole write — including the counter you incremented three lines earlier.

Schema evolution that refuses to guess

When a field is renamed between releases, ZeroZ DB leaves it unset rather than filling it from an unrelated field that happens to share its type. Renames are declared explicitly. This is stricter than the underlying engine's default, on purpose: silently moving data between fields is a corruption that no test catches and no log records.

Ownership safety across JVMs

One JVM owns a store at a time, and that is enforced rather than documented. On a local filesystem an OS file lock does it. On a shared volume — where file locks are unreliable — a heartbeat-renewed lease with a fencing epoch does it, so a challenger takes over only after the lease has expired and the displaced owner stops serving within one heartbeat.

03 · Deployment

One API, three topologies.

Application code is written once. Which mode a node runs in is a deployment decision, not an architectural one.

Mode
What it is
When
Embedded
The store is private to this JVM. No socket, one copy of the graph, full transactions.
A single application, or a store per tenant.
Auto-server
The first JVM to open the store owns and serves it; later ones discover it and become clients. If the owner dies a survivor takes over, and in-flight calls are retried.
Several JVMs, no separate database to operate.
Client
Never owns data. Talks to a dedicated server that does.
A database tier deployed and scaled on its own.

Reads stay local, even on a client.

A client keeps a replica of the graph, refreshed the instant the owner commits rather than by polling, and snapshots swap atomically so a reader never observes a half-applied change. A local read never leaves the heap; a remote query crosses a socket. That gap is why the API distinguishes them — when a read has to be current rather than fast, run it as a query on the owner.

Under the hood

A storage engine, held to account.

Native object persistence

EclipseStore bypasses the object-relational mismatch entirely — no UPDATE statements, no N+1 queries. The in-memory graph is explicitly saved.

Explicit stores, deliberately

A commit persists what you enlisted, and enlisting does not cascade into objects already on disk. That is more work than an ORM's dirty checking, and it is the reason a commit never walks an object graph you did not ask it to walk.

Pure Java, no native code

No embedded engine, no driver, no separate process required. It is a library on your classpath, and in embedded mode it is the only thing in the path between your object and the file.

Limits

What this is not.

A database that does not tell you where it stops is not a database you should trust with a system of record.

One writer at a time.

Writes are serialized. Throughput is bounded by that and by fsync, not by the network. Systems whose bottleneck is concurrent write volume want a different database.

No SQL and no query language.

Queries are Java streams over maintained indexes. That is the design, not a missing feature — but it means no reporting tool will connect to it, and ad-hoc analytics belong somewhere else.

The graph lives in memory.

A store is bounded by the heap you can give it. Very large or cold datasets want a conventional database, or lazily-loaded sub-graphs.

The API is still moving.

The transaction, mode and schema APIs are the settled parts; the guide states which. Pin your version and read the changelog before upgrading.

How it is verified.

The test suite kills a JVM mid-write and re-opens the store to prove nothing acknowledged was lost. A multi-process harness runs concurrent clients across real JVMs against invariants — balances that must sum, counters that must not skip, unique indexes that must hold — then re-opens and checks them again. It is published on Maven Central and it is in use in a real application. It is a young project and it does not claim otherwise.

Documentation ↗

The guide, the API, and the limitations.

Maven

<dependency>
    <groupId>com.zeroz4j</groupId>
    <artifactId>zerozdb</artifactId>
</dependency>

latest version on Maven Central ↗