MindooDB Blog

MindooDB for Kotlin and Java

Karsten Lehmann 4 November 2026 09:00:00

MindooDB is an open-source (Apache 2.0), end-to-end encrypted, local-first document database. Until now you could use it from Node.js, from the browser and from React Native. From today there is a fourth way: a Kotlin client for the JVM, which you can also call from Java.

Why the JVM

A lot of software that companies depend on talks Java, and often only Java. ERP and document management systems, workflow engines, mail and directory servers, and platforms like HCL Domino come with Java APIs and sometimes nothing else. If MindooDB data is supposed to flow into such a system, or out of it, the integration has to run where that API runs: in a JVM.

There is a personal reason as well. At Mindoo we do customer projects in Kotlin and Java ourselves, and many developers we work with come from that world. They know Spring, Ktor, Gradle and coroutines, and they should not have to learn a new runtime before they can try an encrypted, local-first database.

Two components, one engine

The obvious way to support the JVM would have been a port: rewrite the engine in Kotlin. We decided against it. MindooDB’s core does a lot of careful work: signing and encrypting every change, CRDT merging with Automerge, access control with witness receipts, key distribution, the sync protocol. A second implementation would have to stay identical down to the last byte, forever, and every security fix would have to be made twice.

So the JVM support consists of two small pieces around the engine we already have:

  • mindoodb-jvm-bridge, a Node.js sidecar that runs the real mindoodb package. Each user session gets its own child process, which holds that user’s decrypted keys and on-disk replica, so the keys of different users never share memory. The sidecar only listens on loopback, needs a random bootstrap token from a file only your user can read, and checks a per-session token on every message.
  • mindoodb-kmp-client, a Kotlin library that talks to the sidecar over a WebSocket: JSON for calls and events, binary frames with flow control for attachments. Every call is a suspend function or a Flow. From Java you wrap the calls you need in a few lines that return CompletableFutures.
Architecture diagram: your JVM application contains the mindoodb-kmp-client for Kotlin and Java. It talks over a loopback WebSocket to the mindoodb-jvm-bridge, where a Node.js supervisor starts one process per user that runs the MindooDB engine with that user's keys and keeps an encrypted replica on disk. That process syncs, end-to-end encrypted, with a MindooDB server that only stores ciphertext, the same server that Haven, Node.js, browser and React Native clients sync with

Because the JVM side drives the same engine, everything stays compatible. A tenant created from Kotlin can be opened in Haven, and a join request from Haven can be approved by a Kotlin backend.

What you can do from Kotlin and Java

  • Tenants and users: create a tenant on your machine, publish it to a MindooDB server, let new users and devices join, and rename the tenant. The join flow shows the admin the key fingerprints of the joining device before approving, as Haven does.
  • Sync: a background loop keeps the local replica in sync with the server, including attachments. You can sync now with one call and watch the progress of every push and pull.
  • Documents: create, read, update and delete, with batch operations, history and time travel. Updates are merged at the version you read, so concurrent edits by other users are not lost. Field types work as in TypeScript: collaborative text, atomic strings, counters, timestamps, rich text with formatting, and text cursors.
  • Attachments: streamed in both directions, with adapters for InputStream.
  • Queries and views: the complete view language as a Kotlin DSL, live queries, full-text search, joins across databases, and virtual views with categories and totals, also across several databases.
  • Incremental reads: read what changed since a stored cursor, page by page, with or without decrypting the documents. This is the building block for jobs that mirror MindooDB data into another system.

Here is what creating a tenant and writing a document looks like:

val sidecar = Sidecar.start(Paths.get("../mindoodb-jvm-bridge/dist/index.js"), Paths.get("./data"))
val created = MindooDBBridge.createTenant(
    sidecar.endpoint, tenantId = null, tenantLabel = "Acme",
    adminName = "cn=admin/o=acme", adminPassword = "…",
    userId = "cn=service/o=acme", password = "…", databases = listOf("orders"),
)
val session = MindooDBBridge.openSession(sidecar.endpoint, created.tenantId, "cn=service/o=acme", "…", listOf("orders"))
val orders = session.database("orders")

val order = orders.documents.create(mapOf("type" to "order", "customer" to "Contoso"))
orders.documents.update(order.id, baseHeads = order.heads) {
    setAtomic("status", "open")
    set("reminders", MindooValue.counter(0))
}
session.publishToServer("https://demo-server1.mindoodb.com", adminPassword = "…")

Getting started

The getting-started page on this website now has a fourth path next to Node.js, the browser and React Native: Kotlin & Java. It takes you from cloning the two projects to a running program that writes and reads an encrypted document.

For everything else there is a longer guide in the MindooDB repository: MindooDB for Kotlin and Java. It covers the field types, rich text, attachments, the view language with joins, virtual views, incremental reads, publishing and sync, the join flow, calling the client from Java, error handling and an operations checklist.

We also updated llms-full.txt, the file we keep for large language models. It now describes the Kotlin API next to the TypeScript one, so a coding assistant can write JVM code for MindooDB that follows our best practices: summary-based queries instead of loading every document, atomic strings for IDs and status values, joins instead of a query per row, stored cursors for incremental jobs.

Drink your own champagne

An API is only as good as what you can build with it. So we did what we always do and used it ourselves: we had a small Haven-like desktop client built in Kotlin with Compose Multiplatform, using nothing but the public Kotlin API and the new guide. The MindooDB Kotlin Demo creates tenants, publishes them to a server, adds users and devices, and lets you browse, edit and sync the documents.

You start by giving the tenant a name. The ID is generated at random, so tenants never collide on a shared server, and the keys are created on your machine:

The start screen of the MindooDB Kotlin Demo: a Create a tenant form with the tenant name Demo Corp, the admin name and password, the user name cn=anna/o=demo with password and the database notes, and a note that the tenant ID is generated at random like in Haven

Publishing takes one click: the MindooDB demo server that Haven offers is preselected, and any other server works too.

The Server and sync page of the demo: a Tenant section with the generated tenant ID and the editable tenant name, and a Publish to a server section where MindooDB Demo Server 1 at demo-server1.mindoodb.com is preselected as a one-click option, with a field for your own server and the admin password

A second user requests access. Their private keys never leave their device; what they send to the admin is a join request with their public keys:

Ben's window of the demo, waiting for the admin: the join request as an mdb://join-request URI with a copy button, Ben's signing key fingerprint to compare with the admin, and fields for the join response, his password and the databases to open

The admin pastes the request and sees who is asking, from which device, and with which keys, before approving. The fingerprint is the same one Ben’s window shows, so the two can compare it, for example on the phone:

Anna's Join requests page: the pasted join request and a Who is asking card with user cn=ben/o=demo, the device label, the signing and encryption key fingerprints, User-Key included, requested keys default set, directory new person, and fields for the device label and the admin password before approving Sequence diagram of the join flow between Ben, Anna as admin and the MindooDB server: Ben creates his user and a join request and sends it to Anna, Anna inspects the name, device and key fingerprints, both compare the fingerprints, Anna approves and the grant is pushed to the server, Anna sends Ben the join response, Ben joins and pulls the directory, and his first sync pulls the data

After joining, Ben’s first sync brings Anna’s documents. The list is a live query, so it follows every change, local or synced, and the editor saves changes against the version you edited:

Ben's document browser in the demo after joining the tenant Demo Corp Europe: the notes database with the documents Team offsite and Quarterly report, and the selected Quarterly report in a JSON editor with tabs for data, attachments and history

The sync page shows what moved for every database, push and pull, documents and attachments:

The sync activity in Ben's window: for the databases directory, notes and userdirectory one line per pull and push of documents and attachments, each with the number of transferred entries and the phase complete

Building the demo was also a test of the API and the guide, and it turned up things we fixed right away: the joining device now shows its own key fingerprint for the comparison, tenant IDs are generated so they cannot collide on a shared server, and a bug in the sidecar made a document that appeared in several query results arrive empty after the first time. The demo also has a test that plays the whole flow through its user interface with two instances against a server, so it keeps working as the API grows.

Status

MindooDB and Haven are no longer beta. The Kotlin and Java client is new and still beta: its API may change while we gather feedback. Both projects build from source today; the sidecar installs with npm install, the Kotlin client as a Gradle included build. They are not on Maven Central or npm yet.

Try it

If you connect MindooDB to a Java system, I would like to hear which one, and what you needed that is not there yet.

Karsten Lehmann