0001: Start with Java; revisit language choices against concrete requirements¶
- Date: 2026-10-28
- Status: Initial working decision, revisitable
- Scope: Initial engine, correctness reference model and future language boundaries
Context¶
The initial deployment target is an engine embedded in a JVM application. Java provides direct host integration, static checking and established diagnostic tooling. A single implementation language also keeps the first correctness model and storage prototype within one build and debugging environment.
The deployment boundary remains experimental. Native in-process embedding and remote service access are different requirements and may lead to different choices.
Working decision¶
Use Java for the initial engine and simple reference model. Keep the initial runtime and tooling focused on one implementation language. Maven is the Java build tool, providing the standard JVM build and dependency workflow. The build foundation establishes Java 25 and the checksum-pinned Maven Wrapper. The documentation website has a separate Python build; it does not add a language or runtime dependency to the engine.
The reference model should use a simpler, independently reasoned algorithm than the optimized implementation. It need not use a different language. Correctness evidence comes from explicit expectations, invariant tests and fault experiments, not merely from compiling two versions of the same logic.
Choose a language for the component's responsibilities and deployment boundary. A future Clojure or Scala client does not require rewriting the storage engine. A future native component does not require moving temporal semantics into it. Design those boundaries when there is a concrete consumer or experiment; do not create placeholder modules for every possible language now.
Alternatives and tradeoffs¶
| Option | Reasons to consider it | Main tradeoff for this project |
|---|---|---|
| Java | Direct JVM embedding, static checking and established diagnostic tooling | Object allocation, GC, off-heap lifetimes and layout still need deliberate engineering and measurement |
| Clojure | Expressive immutable data transformations, interactive exploration and a potentially concise temporal model | Another language and its tooling to maintain; fewer lines alone do not establish maintainability or performance |
| Scala | Strong types and functional modeling within the JVM | Another language and toolchain to maintain without a demonstrated need in the initial engine |
| Rust | Native embedding, explicit resource control and components where runtime footprint is a primary constraint | When combined with Java, native ownership, ABI, packaging and debugging responsibilities |
| Go | A Go embedding target, independently deployed service or useful operational tool | A second language/runtime and an integration boundary; it does not automatically improve a JVM engine's internals |
Clojure can require less source code for particular transformations or models. The useful comparison is total effort to implement, test, explain, debug and change the same behavior. Immutable in-memory collections do not by themselves provide durable history or a correct commit protocol. A small comparative experiment can compare the same temporal behavior and maintenance task before introducing another production language.
What the existing projects demonstrate¶
Primary sources checked on 2026-10-28. Development-branch source links describe current implementation evidence, not the language composition of every release.
- Kafka: its history includes substantial Scala, but current storage internals
include Java, such as
LocalLog.java. Apache's modularization work includes replacing remaining Scala with Java. Kafka is not evidence that a new engine must choose Scala, nor should it be described as wholly Java. - XTDB: current core sources include both Clojure expression processing and Kotlin bitemporal processing. Its architecture already combines JVM execution, bitemporality, columnar Arrow storage and object storage. It is a relevant design reference and comparison target; copying its language mix is not a substitute for testing MeridianDB's requirements.
- Arrow Java: its memory implementation supports off-heap buffers independently of C++. Particular modules, including Dataset, use native bindings. Evaluate dependencies at the module level rather than assuming that all Arrow use is either pure Java or native.
- Native interoperability: Java's Foreign Function and Memory API provides a route to native code and memory. A binding still needs explicit ownership, lifetime, ABI, error and platform-distribution contracts. These costs belong in any comparison with an entirely Java implementation.
Conditions for revisiting the decision¶
- Embedding requirements: confirm the first host, memory budget and startup constraints before committing to the core. A firm need for lightweight, in-process use from non-JVM hosts makes Rust a serious whole-engine alternative. A client calling a server is a different requirement from native embedding.
- Developer effectiveness: if a Clojure or Scala experiment is undertaken, compare the same temporal contract, tests and maintenance task. Evaluate understandability and debugging effort as well as code volume and speed.
- Measured engine constraints: after a correct durable prototype exists, profile ingestion, temporal lookups, scans, compaction and recovery. Include total process memory, allocation and latency distributions under a stated load.
- Bounded native evaluation: when profiling identifies a relevant bottleneck, compare a Java improvement with a native candidate under identical semantics and durability. Include data conversion, boundary crossings, failure behavior and delivery complexity in the result. Agree workload targets before treating a speedup as sufficient justification.
- Real consumers: reassess language APIs and deployment assumptions before stabilizing the public interface, using actual integration needs.
A change to this decision should record the requirement or experiment, evidence, alternatives and migration consequences. Update the current decision and its related documentation together, preserving the dated rationale. There is no permanent ban on another language and no automatic obligation to adopt one.