River is a relational database written in Java, with an embedded API, JDBC access, and a command-line client. Its storage engine uses MVCC, heap pages, B+trees, a write-ahead log, and checkpoints.
The project is also an experiment in building a large system with coding agents: how to specify work, divide it between agents, and review the result.
River is pre-V1 evaluation software. APIs and on-disk formats may change incompatibly; automatic upgrades are not provided. This README describes the
riverfeature candidate as of 2026-09-08. The alpha.2 release notes describe the earlier release.
- Durable heap and B+tree storage, with unique and non-unique indexes.
- Write-ahead logging, group commit, checkpoints, WAL rotation, recovery of committed transactions, and repair of torn checkpoint pages.
- Concurrent MVCC sessions with read-committed, repeatable-read, and serializable isolation.
- Atomic data and catalog changes, key and range locks, deadlock resolution, statement rollback, and named savepoints.
- Offline backup, restore, and physical file inspection.
- Tables, composite keys and indexes, views, sequences, identities, defaults,
NOT NULL,CHECK,UNIQUE, and foreign keys. - Integer and floating-point types,
DECIMAL(p,s)up to 38 digits,BOOLEAN,VARCHAR(n),DATE,TIME(p), and timestamps with or without time zones. - Multi-row
INSERT,UPDATE, andDELETE; expressions and SQL three-valued logic. INNERandLEFTjoins using nested-loop, hash, and merge strategies.- Aggregation,
GROUP BY,HAVING,DISTINCT, ordering, limits, and disk spill. UNIONandUNION ALL, including parenthesized expressions and result ordering and limits.- Derived tables and scalar,
EXISTS,IN,NOT IN, and correlated subqueries within the supported SQL shapes. ANALYZE,EXPLAIN, andEXPLAIN ANALYZE, with stored statistics and execution counters.- Streaming JDBC 4.3 results and prepared parameters. Installed clients use the
generated TLS 1.3 and token-authenticated
client.propertiesconfiguration. Embedded applications may use the embedded API directly.
The SQL conformance profile and JDBC support matrix describe supported features and omissions. The main size and capacity limits are summarized below.
These limits apply to individual tables, rows, keys, or SQL clauses as stated. Available memory, temporary disk space, and concurrent work may limit an operation sooner. Capacity limits describe what the formats can represent; they are not claims that River has been tested at that scale.
The 4 MiB result-row limit applies to one row, not the whole query result. A query can return more than 4 MiB in total. JDBC streams rows as the client reads them; sorting and other operations that retain results also need memory and, when they spill to disk, temporary disk space.
| Area | Current limit |
|---|---|
| Table columns | Up to 1,024 columns per table. The combined row must also fit the stored-row limit below |
| Result columns and SQL clauses | Up to 1,664 columns in a result row, 1,664 expressions in a GROUP BY list, and 1,664 expressions in an ORDER BY list |
| One stored row | Up to 16,216 bytes after encoding, including row metadata. A stored row must fit within one 16,384-byte page |
Text (VARCHAR(n)) |
The type can represent n up to 65,535 Unicode scalar values (not bytes). A table definition must fit the stored-row limit using each column's declared maximum: four bytes per scalar value, plus metadata. This makes the maximum n for a stored column smaller, even if its actual values would be short |
Decimal (DECIMAL(p,s)) |
Up to 38 total digits (p). The number of digits after the decimal point (s) must be between zero and p |
| Indexes and keys | Up to 64 secondary indexes per table and 32 columns per key. The combined encoded column values in one index key must fit within 3,072 bytes |
| Table row IDs | Row IDs range from 1 through 4,294,967,294 per table. This is the ID range, not a guaranteed usable table size; storage capacity and page-address limits also apply |
| Joins | Up to 64 table references in one left-to-right chain of INNER/LEFT joins. Referencing the same table twice counts twice. Join reordering and partitioned hash-join spill are unfinished |
| One result row | Up to 4 MiB (4,194,304 bytes) of encoded row payload. This is a per-row limit, not a limit on the total query result |
| Results held for sorting or other processing | Stored in pages and able to spill to temporary disk. Total capacity depends on configured memory budgets, temporary disk space, and address limits |
| Savepoints | The number of savepoints is limited by the session's resource budget. Exhausting that budget returns RESOURCE_EXHAUSTED |
| Network access | Clients must connect from the same machine over a loopback address. Remote deployment is unsupported |
| JDBC | Multiple statements may be open per connection, but only one query can be active at a time. Finish reading or close that query before running another. Result sets are forward-only and read-only: they cannot scroll backward or update rows. Callable statements are unsupported |
The main sources for these limits are
SqlShapeLimits,
SqlTypeDescriptor,
HeapPage, and
IndexedTableLimits.
The current river server start candidate workflow has been validated on
macOS/APFS and Linux/ext4/XFS. Windows/NTFS validation remains pending. SQL/
security audit collection is deferred pending a concrete performance-neutral
design. The foreground server creates or reopens
the database, publishes security/client.properties, and accepts only
authenticated connections. Backup and restore are offline; replication,
failover, and online schema migration are not available.
Focused recovery, concurrency, and capacity tests pass, but the full crash, isolation, fault-injection, and long-running growth tests required for release are unfinished. Billion-row workloads have not been qualified.
TPC-C-derived workloads run through the diagnostic tools. Correctness and
scaling revalidation is still open, followed by further throughput work. Local
samples are not audited TPC-C results and must not be reported as tpmC.
The performance checkpoints record results
and their limits; the delivery queue lists the work
still required for performance and cross-database comparisons.
Building River requires JDK 25. Gradle verifies dependency checksums. JVM
compilation, tests, and development commands use the normal JDK. Native
packaging additionally requires a GraalVM JDK 25 installation for the host
OS and architecture, with GRAALVM_HOME set to its installation directory.
Native packaging is pre-alpha. The macOS arm64 executable has been exercised; Linux and Windows native builds have not yet been validated.
Build the self-contained native executable:
GRAALVM_HOME=/path/to/graalvm-jdk-25 ./gradlew --no-daemon :river-server-app:nativeCompileNative builds use O3 optimization. For profile-guided optimization (PGO), see the native build guide.
The result is bin/river (bin/river.exe on Windows). Copy that executable
alone to run the client or start the server. The server writes the generated
client configuration under its data directory:
bin/river server start --datadir=/absolute/path/to/database --port=9191In another terminal, bin/river ps lists local servers with the exact HOST:PORT
accepted by bin/river stop HOST:PORT. Bare bin/river stop stops the default
instance at ~/.river/default. Stop waits for graceful shutdown; it does not
delete the database.
Use the reported security/client.properties path with the SQL client or JDBC:
bin/river /absolute/path/to/database/security/client.properties < setup.sqlThe CLI reads semicolon-terminated SQL, emits tab-separated rows, and stops at the first error. The CLI reference and database how-to describe the generated client-file workflow.
Run the tests:
./gradlew testFor the clean release checks:
./verify./verify runs clean check: tests plus source and dependency policy checks.
For River-specific throughput diagnostics:
./make.sh
tools/tps-test.shResults use the current Git branch as their version label. Use
--version=<label> to give a run a different name.
The first goal is a capable relational database. Later work may add storage and queries across different data types to help agents manage context. NQL integration may also provide queries and updates across databases and files. These are future directions, not current features.
The delivery queue lists planned work, with details in the tickets. See the manifesto for engineering principles and AGENTS.md for contributor workflow instructions.
River uses the GNU Affero General Public License v3.