# nitrite: an embedded NoSQL document store for Java

> Nitrite is an open source embedded document database for Java that runs inside your application with no server, backed by either mvstore or RocksDB, with schemaless collections, typed object repositories, indexing and transactions. It is aimed at desktop, mobile and small web applications where a client-side store is the right shape.

**nitrite/nitrite-java** — NoSQL embedded document store for Java

- Repository: https://github.com/nitrite/nitrite-java
- Website: https://bit.ly/no2db
- Stars: 920 · Forks: 96
- Language: Java
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/nitrite-nitrite-java

## Embedded, serverless, and aimed at the client

Nitrite stands for NOsql O2bject, and the README describes it as an open source nosql embedded document store. It supports both in-memory and file based persistent stores, and the positioning line is that it is an embedded database ideal for desktop, mobile or small web applications.

Embedded is the word that matters. There is no server process, no network protocol and no separate deployment. The database is a library inside your JVM, with the storage engine writing to a file on the same disk as your application. That shape fits three situations in particular. A desktop application that has to keep working when the network is not there. A mobile application, where a server round trip is too slow for the UI. A small web application whose data fits comfortably on one node.

The feature list in the README is a reasonable summary of what an embedded document store is expected to do. It is embedded and serverless, with a simple API. It is document-oriented, with a schemaless document collection and an object repository. It has indexing and full-text search, a simple query API, transaction support, schema migration support and encryption support.

The last two entries are the ones that separate Nitrite from a map wrapper. Schema migration matters when your application updates and the stored documents have to be transformed, and encryption matters when the data lives on a device you do not control.

Android compatibility is listed at API level 26, and the README points at a Todo application as an Android usage demo.

## Storage engines are loaded as modules

The storage engine is not baked in. The README lists extensible storage engines, mvstore and RocksDB, and the repository layout shows exactly how that is packaged: `nitrite-mvstore-adapter/` and `nitrite-rocksdb-adapter/` are separate modules, alongside `nitrite/`, `nitrite-support/`, `nitrite-jackson-mapper/`, `nitrite-spatial/`, `nitrite-bridge/` and `nitrite-native-tests/`.

That modularity is visible in the initialization code, where the storage module is built first and then loaded:

```java
// create a mvstore backed storage module
MVStoreModule storeModule = MVStoreModule.withConfig()
    .filePath("/tmp/test.db") 
    .compress(true)
    .build();

// or a rocksdb based storage module
RocksDBModule storeModule = RocksDBModule.withConfig()
    .filePath("/tmp/test.db")
    .build();


// initialization using builder
Nitrite db = Nitrite.builder()
        .loadModule(storeModule)
        .loadModule(new JacksonMapperModule())  // optional
        .openOrCreate("user", "password");
```

Three things to notice. The engine is chosen by the application, so the storage dependency is your choice rather than a transitive one. The mapper is a separate module too, `JacksonMapperModule`, and it is marked optional in the example, so a project that stores only documents does not need a JSON mapper on the classpath. And the database is opened with credentials, `openOrCreate("user", "password")`, which is what makes the encryption support reachable rather than theoretical.

The two engines are not interchangeable in every respect. The mvstore configuration in the example exposes a compression switch, while the RocksDB configuration does not, so the options available to you depend on the engine you pick. The README does not document a comparison, so the choice is one you make on the engine's own merits.

## Installing through the bill of materials

Nitrite is installed with Maven or Gradle, and the README's instruction is to add the Nitrite bill of materials first, then the required dependencies. The BOM exists so that the version is declared once and every Nitrite artifact moves together.

For Maven:

```xml
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.dizitart</groupId>
            <artifactId>nitrite-bom</artifactId>
            <version>[latest version]</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.dizitart</groupId>
        <artifactId>nitrite</artifactId>
    </dependency>

    <dependency>
        <groupId>org.dizitart</groupId>
        <artifactId>nitrite-mvstore-adapter</artifactId>
    </dependency>
</dependencies>
```

For Gradle:

```groovy
implementation(platform("org.dizitart:nitrite-bom:[latest version]"))

implementation 'org.dizitart:nitrite'
implementation 'org.dizitart:nitrite-mvstore-adapter'
```

The Gradle form is the cleaner of the two, because the platform notation puts the BOM constraint in the dependency block itself rather than in a separate dependencyManagement section.

Two practical notes. The README writes the version as `[latest version]`, a placeholder to be replaced with the actual release, so the version you write has to come from the release list rather than from the documentation. And the artifacts are published under the `org.dizitart` group id, which does not match the `nitrite` repository name, so a search for `org.dizitart:nitrite` in your dependency manager is a better starting point than a search for the repository.

The group id is also visible in the javadoc badge, which points at javadoc.io for `org.dizitart/nitrite`.

## Documents and object repositories are two views of the data

Nitrite exposes the same data through two APIs, and understanding when to use which is most of the design.

The document API is schemaless. You create a collection and put arbitrary key value pairs into it:

```java
// Create a Nitrite Collection
NitriteCollection collection = db.getCollection("test");

// Create an Object Repository
ObjectRepository<Employee> repository = db.getRepository(Employee.class);
```

The object API is typed. You map a POJO with annotations and the repository handles serialization for you:

```java
@Entity(value = "retired-employee",     // entity name (optional), 
    indices = {
        @Index(value = "firstName", type = IndexType.NON_UNIQUE),
        @Index(value = "lastName", type = IndexType.NON_UNIQUE),
        @Index(value = "note", type = IndexType.FULL_TEXT),
    })
public class Employee implements Serializable {
    // provides id field to uniquely identify an object inside an ObjectRepository
    @Id
    private long empId;
    private Date joinDate;
    private String firstName;
    private String lastName;
    private String note;

    // ... public getters and setters
}
```

Two details in that annotation block are worth pausing on. The `@Id` field is what uniquely identifies an object inside a repository, and it does not have to be the primary key you would choose in a relational database; a long counter is fine. And the `indices` attribute declares indexes declaratively, including a `FULL_TEXT` type, so a full-text index on a text field is part of the class definition rather than a runtime call.

The README's CRUD example then shows both styles side by side, inserting a `Document` built with `Document.createDocument("firstName", "John").put("lastName", "Doe")` into the collection, and inserting an `Employee` instance into the repository, with `collection.find(...)`, `collection.update(...)` and `collection.remove(...)` for the document side.

That duality is genuinely useful for real applications, where a typed model owns the business data and a document collection holds loosely structured things such as settings or imported records. It also means the schemaless claim needs qualifying: the collection itself is schemaless, but the typed repository imposes a fixed shape on the documents it manages.

## Indexes and the query builder

Querying uses a small fluent builder that produces a filter expression, and the two index types line up with the two ways you query.

```java
// create document index
collection.createIndex(indexOptions(IndexType.NON_UNIQUE), "firstName", "lastName"); // compound index
collection.createIndex(indexOptions(IndexType.FULL_TEXT), "note"); // full-text index

// create object index. It can also be provided via annotation
repository.createIndex(indexOptions(IndexType.NON_UNIQUE), "firstName");
```

A `NON_UNIQUE` index is an ordinary index, and several fields in one call create a compound index. A `FULL_TEXT` index backs the text search operator. The last line matters for the typed side, since it says the index can be declared on the class through the annotation instead of being created at runtime, which is the better choice when the schema is known at compile time.

The query builder composes the same way for both APIs:

```java
DocumentCursor cursor = collection.find(
    where("firstName").eq("John")               // firstName == John
    .and(
        where("data").elemMatch("$".lt(4))      // AND elements of data array is less than 4
            .and(
                where("note").text("quick")     // AND note field contains string 'quick' using full-text index
        )       
    )
);
```

Three operators are visible here. `eq` is equality, `elemMatch` applies a condition to the elements of an array field, and `text` is the full-text operator that requires a `FULL_TEXT` index to be useful. The last point is the practical one: the `text` operator is only as good as the index behind it, and an unindexed text search over a large collection is the kind of query that turns an embedded database into a slow one.

Results come back as a cursor, and the object API returns a typed cursor, where `cursor.firstOrNull()` gives you a single object.

Note that the query language is Nitrite's own. It is not SQL and not MongoDB's query syntax, so a team porting from either will need to translate, and the README does not document the full operator set.

## Sessions and transactions

Transaction support is part of the feature list, and the README shows the shape of it:

```java
try (Session session = db.createSession()) {
    try (Transaction transaction = session.beginTransaction()) {
        NitriteCollection txCol = transaction.getCollection("test");

        Document document = createDocument("firstName", "John");
        txCol.insert(document);
```

The transaction is scoped to the session, and the collection handle you get inside the transaction is a different object from the one you would get from the database directly. That distinction is the point: writes made through the transactional collection are part of the transaction and can be rolled back, while writes through the plain collection are not.

The session is also try with resources, so closing it releases it. For an embedded database, a session is a relatively heavyweight object that holds transaction state, and the pattern of acquiring one per unit of work is what keeps that cost bounded.

The README does not document isolation levels, conflict detection behaviour, or whether concurrent transactions on the same collection are serialised. If your application writes to the same collection from several threads, that is a question worth answering from the source or the documentation site before you rely on it, rather than assuming the behaviour of a server database.

Alongside transactions, the feature list includes schema migration support, which is the piece that matters most when your application version changes and the stored documents need to be brought up to a new shape.

## Deprecations, licence and a steady release train

The README carries a Deprecation Notice that is easy to miss and matters if you are evaluating the wider Nitrite family. Nitrite DataGate and Nitrite Explorer are now deprecated and no longer maintained.

That narrows the picture. The embedded library is the live project, and the browser based explorer and the DataGate tooling are not. If you were planning to use the Explorer to inspect a database file, that tool is gone from the maintenance path, and the alternatives are the Kotlin extension and the Flutter port, which the README points to separately as Potassium Nitrite and nitrite-flutter.

The licence field on the repository says Apache-2.0, and there is a LICENSE.md alongside a THIRD-PARTY-NOTICES file, which is the expected pairing for a library that embeds RocksDB and mvstore. Apache-2.0 includes an explicit patent grant and requires the notice to be preserved, which is the usual arrangement for a library you embed in an application you ship.

On maintenance, the release history is dense. The tags are v5.3.0 on 2026-08-31, v5.4.0 on 2026-10-05 and v5.4.1 on 2026-10-17, with the last push on 2026-10-19. Patch releases inside a minor line, and a minor line every few weeks, is what a library used as a client-side data store tends to look like, because it has to keep up with the platforms it runs on.

One warning from the README deserves repeating. There are breaking api changes in version 4.x, and it asks you to read the guide before upgrading from 3.x.x. That tells you the API has moved before and will move again, which is worth factoring into how you pin the version and how you plan schema migrations across upgrades.

## Conclusion

Adopt nitrite when your Java application needs a local store with query and index support and you want it embedded rather than hosted, especially on Android where API level 26 and above is supported. Skip it if you need a server the rest of your fleet can reach, or if you need a distributed database with replication. Verify first that the storage engine you pick, mvstore or RocksDB, is the one you want to ship, because the adapter is a separate Maven or Gradle dependency, and that your POJOs map cleanly through the Jackson mapper module. The licence field on the repository says Apache-2.0, the last push was on 2026-10-19, and the current release is v5.4.1.

## FAQ

### What is Nitrite and who is it for?

Nitrite is an open source nosql embedded document store written for Java, supporting in-memory and file based persistent stores. The README positions it as ideal for desktop, mobile or small web applications, and it lists Android compatibility at API level 26.

### How do I install Nitrite in a Java project?

Add the Nitrite bill of materials first, then the dependencies. In Maven you import `org.dizitart:nitrite-bom` as a pom in dependencyManagement and depend on `org.dizitart:nitrite` plus an adapter such as `org.dizitart:nitrite-mvstore-adapter`. In Gradle you use `implementation(platform("org.dizitart:nitrite-bom:[latest version]"))`.

### Which storage engines does Nitrite support?

The README lists extensible storage engines, mvstore and RocksDB, and the repository contains separate `nitrite-mvstore-adapter` and `nitrite-rocksdb-adapter` modules. You build a module with `MVStoreModule.withConfig()` or `RocksDBModule.withConfig()` and load it with `Nitrite.builder().loadModule(storeModule)`.

### What is the difference between a Nitrite collection and an object repository?

A collection is schemaless and holds documents built from arbitrary key value pairs, while an object repository is typed and maps to an annotated POJO with an `@Id` field. The README also notes that indexes can be created on a repository at runtime or declared through the `@Index` annotation on the class.

### Does Nitrite support transactions and full-text search?

Yes. Transactions are opened through a session with `session.beginTransaction()`, and the collection obtained from the transaction is the handle that participates in it. Full-text search uses the `text` operator in the query builder, backed by an index created with `indexOptions(IndexType.FULL_TEXT)`.

### What licence is Nitrite released under?

The licence field on the repository says Apache-2.0, with a LICENSE.md and a THIRD-PARTY-NOTICES file. The releases are v5.3.0 on 2026-08-31, v5.4.0 on 2026-10-05 and v5.4.1 on 2026-10-17, and the README notes that Nitrite DataGate and Nitrite Explorer are deprecated and no longer maintained.

## Sources

- [License: Apache-2.0](https://github.com/nitrite/nitrite-java/blob/main/LICENSE)
- [nitrite/nitrite-java on GitHub](https://github.com/nitrite/nitrite-java)
- [Project website](https://bit.ly/no2db)
- [README](https://github.com/nitrite/nitrite-java/blob/main/README.md)
- [Releases](https://github.com/nitrite/nitrite-java/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nitrite-nitrite-java
