-
Notifications
You must be signed in to change notification settings - Fork 1
In Memory
What this page covers: the ephemeral in-memory backend — Storages.createInMemory() (the
no-arg form you almost always want), why it's the go-to for tests, CI, and prototyping, the fact
that data lives only as long as the JVM, and its two deliberate non-properties: transactions exist
but provide no isolation, and there are no migrations. Everything else (CRUD, queries,
indexing) is identical to every other backend.
📌 Note — because the API is identical across backends, an in-memory store is a faithful stand-in for a real database in tests. Write your code against
Storage/Repository, run it against in-memory in CI, and ship it on SQL/Mongo — the data-access code never changes. See Choosing a Backend.
import br.com.finalcraft.everydatabase.*;
import br.com.finalcraft.everydatabase.codec.JacksonJsonCodec;
import java.util.Optional;
import java.util.UUID;
// 1. Describe the entity once — key type FIRST, entity type second.
EntityDescriptor<UUID, PlayerData> PLAYERS = EntityDescriptor.builder(UUID.class, PlayerData.class)
.collection("players")
.keyExtractor(PlayerData::getUuid)
.codec(new JacksonJsonCodec<>(PlayerData.class)) // in-memory requires a JSON codec
.build();
// 2. Open it — no config, no server, no setup.
InMemoryStorage storage = Storages.createInMemory();
storage.init().join();
// 3. Use it — exactly like any other backend.
Repository<UUID, PlayerData> repo = storage.repository(PLAYERS);
UUID id = UUID.randomUUID();
repo.save(new PlayerData(id, "Alice", 100)).join(); // upsert
Optional<PlayerData> alice = repo.find(id).join(); // -> Optional[Alice]
storage.close().join(); // clears the storecreateInMemory() returns the concrete InMemoryStorage type. The PlayerData entity is the same
plain Jackson POJO used throughout the wiki (Quick Start).
📌 Note — every I/O call returns a
CompletableFuture. In-memory operations complete immediately on the calling thread, but the contract is the same async surface as every other backend —.join()for brevity, compose withthenApply/thenCompose, no blocking variants. See The Async API.
InMemoryStorage mem = Storages.createInMemory(); // preferredThere is no configuration to give — data exists only while the JVM runs, so there is nothing to point it at.
⚠️ Gotcha — anInMemoryConfigoverload exists (Storages.createInMemory(new InMemoryConfig())) only so the genericStorages.create(StorageConfig)can dispatch a runtime-selected config to this backend. It ignores its argument —InMemoryConfigcarries no fields. Prefer the no-argcreateInMemory(); reach for the config overload only when a runtime-selectedStorageConfigflows throughcreate(...).
// Runtime-selected backend: in-memory is just one of the cases create(...) dispatches.
StorageConfig config = loadConfig(); // may be SqlConfig / MongoConfig / InMemoryConfig / ...
Storage storage = Storages.create(config); // an InMemoryConfig lands here, fields-and-all ignoredData lives in JVM heap and disappears when the process exits or you call close() (which clears the
store). There is nothing on disk and nothing to back up.
The entity is held as a live JVM object (its codec-parsed form), so reads are instant. Secondary
indexes are kept as an in-memory Map<value, Set<key>>, so indexed queries are real lookups, not
scans:
EntityDescriptor<UUID, PlayerData> PLAYERS = EntityDescriptor.builder(UUID.class, PlayerData.class)
.collection("players")
.keyExtractor(PlayerData::getUuid)
.index(IndexHint.integer("score")) // backed by an in-memory map
.codec(new JacksonJsonCodec<>(PlayerData.class))
.build();
repo.findBy("score", 100).join();
repo.query(Query.range("score", 50, null)).join(); // score >= 50 (inclusive, null = open end)Querying an undeclared field throws IllegalArgumentException, the same as on every backend, so
test code exercises the same declaration rules as production. See Indexing & Queries.
⚠️ Gotcha — in-memory requires a JSON codec (codec.isJsonCodec());repository(...)throwsIllegalArgumentExceptionfor a non-JSON codec likeJacksonYamlCodec(which only Local Files accepts). The key contract is the usual one — a stable, uniquetoString()of ≤ 255 characters. See Codecs and Entities, Keys & Collections.
InMemoryStorage implements tx.TransactionalStorage, so code written against the
transaction API runs against in-memory storage — useful for testing transactional logic without a
real database:
storage.inTransaction(scope -> {
Repository<UUID, PlayerData> txRepo = scope.repository(PLAYERS);
return txRepo.save(alice)
.thenCompose(__ -> txRepo.save(bob));
}).join();
⚠️ Gotcha — there is no real isolation. Writes inside the transaction are visible to concurrent readers immediately, andscope.rollback()is a no-op (there's nothing buffered to undo). The interface contract (commit/rollback completes) is honoured so the shape of your code works, but in-memory must not be used to test rollback semantics or isolation — only that transactional code paths execute. For real ACID behavior in a test, use H2 (full SQL transactions, embedded, no server). See Transactions.
InMemoryStorage does not implement schema.SchemaAwareStorage — there is nothing to migrate
in an ephemeral store, and no _schema_migrations tracking. Calling migration methods isn't part of
this backend's surface.
📌 Note — if your code calls
storage.register(...).migrate()and you want it to exercise the migration path in a test, run that test against H2 (which implementsSchemaAwareStorage) rather than in-memory. See Schema Migrations and the matrix in Choosing a Backend.
Optimistic locking isn't enforced either (in-memory and local files don't enforce it — see Optimistic Locking).
🧭 Decision — choose in-memory for tests, CI, and prototyping: instant, ephemeral, zero setup, no external service. It exercises the same
Storage/RepositoryAPI, the same index/query rules, and the same codec requirements as production — so it's a faithful fast stand-in. Do not rely on it for transaction isolation or migrations (it has neither); when a test needs those, reach for H2 (embedded, full SQL features, still no server). Never use in-memory for data you need to keep — it vanishes with the JVM.
- Choosing a Backend — the capability matrix and where in-memory fits.
- Quick Start — the minimal describe → open → use → close round-trip.
-
The Async API — the
CompletableFuturemodel every call shares. - H2 — the embedded alternative when a test needs real transactions or migrations.
- Transactions — what "no isolation" means and which backends provide real isolation.
- Schema Migrations — why in-memory has none.
- Indexing & Queries — the index/query rules in-memory shares with every backend.
- Codecs — why in-memory requires a JSON codec.
- Running the Tests — how the suite uses embedded backends (no Docker) for fast runs.
EveryDatabase · Home · made by Petrus Pradella
Getting Started
Core Concepts
Working with Data
Backends
Manager Module
- Caching & References
- Typed References (Ref)
- Caching Managers
- Cache Policies & Freshness
- Cross-Process Cache Sync
- One Entity, Many Databases
Operations
Advanced
Reference
Contributing