Kookaburra

Query Java objects like a database, right inside your application.

An embedded object database for Java 17+. Store plain annotated objects and query them with a type-safe, Stream-like API or SQL-style OQL, answered from indexes in milliseconds. Data is crash-safe on disk, with transactions at three isolation levels, unique and foreign-key constraints, exact decimals and online backup.

Java 17+Apache-2.0Embedded, no server ANTLR 4 OQLcoverage โ‰ฅ 90%Windows ยท Linux ยท macOS
Kookaburra Studio: data grid
Tutorial

Kookaburra in three minutes

First Studio, operated with the mouse: browse, filter, inspect, export, follow references, edit (form and JSON), constraints, delete with cascade, transactions with commit and rollback, OQL with EXPLAIN, and backup. Then the same features as Java code. English subtitles.

Why Kookaburra

Local data shouldn't need a server or an ORM

Typical pain points when an application needs to keep and query its own objects:

๐Ÿงฑ

ORM + SQL for local state

Mapping layers, schemas and a database server just to persist and filter your own objects.

๐Ÿข

Streams over lists don't scale

list.stream().filter(...) scans everything, every time, and lives only in the heap.

๐Ÿ”ค

Stringly-typed queries

Field names in strings break silently when you refactor.

๐Ÿ”

No way to look inside

Binary stores give you nothing to browse, fix or export data with.

๐Ÿงน

Integrity by hand

Duplicate keys and dangling references slip in when every check lives in application code.

โš–๏ธ

Races between checks and writes

"Check, then write" breaks under concurrency: two writers both see the old state.

๐Ÿ’ธ

Floating-point money

0.1 + 0.2 is not 0.3 in a double.

๐Ÿ’พ

Backing up a live store

Copying files while the application writes gives an inconsistent copy.

Features

Everything in one small library

Embedded, durable, indexed and queryable, with a desktop tool to look inside.

๐Ÿงญ

Type-safe chained queries

query(Book.class).where(Book::getYear).ge(1960).list() or the generated Book_ metamodel. Refactoring-safe.

โšก

Indexes

Index the fields you filter, sort and group by: milliseconds on 10 M objects.

๐Ÿ”ค

OQL

SELECT / UPDATE / DELETE with the SQL-92 core: CASE, LIKE, DISTINCT, GROUP BY โ€ฆ HAVING.

๐Ÿงฌ

Nested objects

Inheritance, embedded objects at any depth, lists and maps of them (whereAny, MEMBER OF), references with fetch.

๐Ÿ”

Transactions

READ COMMITTED, SNAPSHOT, SERIALIZABLE; optimistic, never blocking; retries built in.

๐Ÿ”‘

Unique constraints

@Unique on a field or a combination of fields; checked atomically at commit.

๐Ÿ”—

Foreign keys

@ForeignKey with RESTRICT, CASCADE or SET_NULL; backlinks with referrers.

๐Ÿ’ฐ

Exact decimals

BigDecimal fields, indexed and compared by value; exact SUM and arithmetic.

๐Ÿช

Lifecycle callbacks

@PrePersist, @PostLoad and friends, plus entity listeners.

๐Ÿ’พ

Online backup

A consistent snapshot while writers keep going; restore = open.

๐Ÿ“ฆ

Large values

Long texts and binaries such as images, several MiB per value.

๐Ÿ›ก

Durable & consistent

Committed data survives crashes; readers see consistent snapshots; deleted space is reclaimed.

๐Ÿงฉ

Dynamic API

A self-describing schema with evolution. Open any database without its classes.

๐Ÿ—œ

Compact & fast restart

About 150 B per object on disk; opens in milliseconds with a tiny heap.

๐Ÿ–ฅ

Kookaburra Studio

Several databases, transactions at any level, backlinks, constraints, image preview, OQL console, backup. One jar for every OS.

๐Ÿ’ฝ

Tested on disk

Every shared suite runs in memory and on disk; โ‰ฅ 90% coverage; a 10 M-object stress run.

How it works

A library inside your application

Your application uses the ObjectDB API; data lives in a directory that Studio and backups use
  • Annotate plain classes: Kookaburra stores them in a data directory, or in memory. No server, no schema scripts.
  • Query with method references or OQL; indexes answer, and explain() shows which one.
  • Write objects, batches or transactions: commits are atomic and durable, constraints are checked at commit.
  • Look inside with Studio, or call db.backup(dir) for a consistent copy while the application runs.
Quick start

Three steps

Add the dependency, annotate a class, query it.

1 ยท pom.xml
<dependency>
    <groupId>com.github.paganini2008</groupId>
    <artifactId>kookaburra-core</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
2 ยท Entity
@Entity
public class Book {
    @Id long oid;
    @Version long version;
    @Index String title;
    @Index Genre genre;
    @Index(IndexKind.ORDINAL) int year;
    double price;
    // constructors, getters โ€ฆ
}
3 ยท Store & query
try (ObjectDB db = ObjectDB.open(Path.of("data"))) {
    db.batch()
      .insert(new Book("Dune", Genre.SCIFI, 1965, 9.99))
      .insert(new Book("Neuromancer", Genre.SCIFI, 1984, 12.50))
      .insert(new Book("Hyperion", Genre.SCIFI, 1989, 14.20))
      .commit();

    List<Book> books = db.query(Book.class)
            .where(Book::getGenre).eq(Genre.SCIFI)
            .where(Book::getYear).ge(1960)
            .orderBy(Book::getYear).desc()
            .limit(2)
            .list();
    System.out.println(books.stream().map(Book::getTitle).toList());
}
Output
[Hyperion, Neuromancer]
Examples

Use cases

Each example: input โ†’ code โ†’ output, from a real run on six books.

Group & aggregate

Count and average price per genre.

db.query(Book.class)
  .groupBy(where(Book::getGenre))
  .agg(Q.count().as("books"),
       Q.avg(where(Book::getPrice)).as("avgPrice"))
  .toList();
SCIFI books=4 avg=11.36 FANTASY books=1 avg=10.00 CRIME books=1 avg=7.40

OQL for tools & scripts

Text queries with parameters.

db.oql("SELECT b.author, b.year FROM Book b " +
       "WHERE b.year < :y ORDER BY b.year")
  .param("y", 1950).rows();
J.R.R. Tolkien 1937 Raymond Chandler 1939

Generated metamodel

Compile-time field constants from the annotation processor.

db.query(Book.class)
  .filter(Book_.tags.contains("classic"))
  .filter(Book_.price.lt(10.0))
  .count();
2

Admin without classes

Dynamic access to any stored class.

DynamicCollection books = db.dynamic("Book");
books.from()
  .where("author").startsWith("J")
  .list().get(0).get("title");
"The Hobbit"

Safe concurrent edits

Optimistic locking with @Version.

dune.setPrice(11.99);
db.update(dune);
// OptimisticLockException if changed meanwhile
dune=11.99

See the plan

Which index answers each condition.

db.query(Book.class)
  .where(Book::getGenre).eq(SCIFI)
  .where(Book::getYear).between(1950, 1970)
  .explain();
genre = 'SCIFI' [sorted docs card=3] year IN [1950, 1970] [sorted docs card=6]

Transactions

Opt-in transactions; conflicts are detected at commit.

try (Transaction tx = db.begin(Isolation.SNAPSHOT)) {
  Account a = tx.get(Account.class, from);
  tx.update(a.balance(a.getBalance() - 30));
  tx.increment(Account.class, to, "balance", 30);
  tx.commit();
}
ConflictException if `a` changed meanwhile

Nested objects

Paths into embedded objects, lists and maps.

db.query(Shipment.class)
  .whereAny(Shipment::getItems, Item::getSku)
  .contains("pear").list();
db.oql("SELECT s FROM Shipment s " +
       "WHERE 'kiwi' MEMBER OF s.items.sku").list();
[S-1] [S-2]

Texts & images

Large values next to their metadata.

db.insert(new Document("scan", text,
    Files.readAllBytes(Path.of("scan.png"))));
// Base64 in JSON / CSV exports
tested to 16 MiB per value

Constraints & decimals

Unique numbers, cascading foreign keys, exact totals.

@Unique String number;
@ForeignKey(onDelete = OnDelete.CASCADE)
Ref<Customer> customer;
@Index BigDecimal total;

db.query(Order.class).sum(where(Order::getTotal));
duplicate 'A-1' โ†’ ConstraintViolationException sum of 19.90 + 10.15 โ†’ 30.05

SERIALIZABLE

Two on-call doctors both try to go off call.

db.inTransaction(TxOptions.defaults()
    .isolation(Isolation.SERIALIZABLE), tx -> {
  if (tx.query(Doctor.class).where(Doctor::isOnCall)
        .eq(true).count() > 1)
    tx.update(tx.get(Doctor.class, me).offCall());
});
second commit: serialization failure, retried one doctor stays on call

Online backup

Back up while writing; open the copy to restore.

BackupInfo info = db.backup(
    Path.of("backups/2026-10-17"));
try (ObjectDB copy = ObjectDB.open(info.path())) {
  โ€ฆ
}
a complete copy as of one moment
Isolation levels
Three isolation levels and the anomalies each one prevents.
Constraints
@Unique and @ForeignKey with ON DELETE actions.
Online backup
Online backup while the application keeps writing.
Studio OQL console
OQL console with completion and transaction scripts.
Kookaburra Studio

See inside every database

A standalone desktop tool: several databases side by side, transactions, constraints and backlinks, nested objects and binaries, an OQL console, backups.

Studio transaction
Begin (pick the isolation level), Commit, Rollback per database, or BEGIN ISOLATION LEVEL โ€ฆ; COMMIT in the console.
Studio backlinks
"Referenced by": the objects pointing at the selected one; double-click opens one.
Studio constraints
Constraint column: UNIQUE, FK โ†’ Customer ยท CASCADE.
Studio nested inspector
Lists of embedded objects as [ n elements ]; the Key / Value / Type inspector expands every level.
Studio element editor
Element tables for lists and maps: add, remove, reorder.
Studio binary field
Binary fields: format, size, image preview, load / save.
Performance

10 million objects, milliseconds

p50 latency from the kookaburra-bench stress harness (warm-up, repeated runs, consistency checks after every phase).

Query latency (lower is better, log scale)

get(oid)
0.6 ยตs
unique key (OQL)
0.12 ms
eq + range + limit
0.2 ms
count(city = ?)
0.24 ms
top-10 of 2.3 M
0.39 ms
GROUP BY count
4.0 ms
OQL GROUP BY + AVG
244 ms
unindexed full scan
0.81 s

Writes, storage, recovery

MetricResult
bulk insert, batches of 100 (ASYNC)696 k/s
durable inserts, 16 / 128 threads (SYNC)3.2 / 21.2 k/s
compact 10 M objects (1.4 GB)10.4 s
online backup of 10.5 M objects, writers running25.4 s
transfers, 16 threads: SNAPSHOT / RC / SERIALIZABLE136 / 142 / 140 k/s
insert with @Unique / without235 / 258 k/s
atomic increments on one hot counter265 k/s
OQL DELETE of 10.5 M objects, disk after compact1.32 M/s, 0 B
reopen2.0 ms
heap after reopen6.5 MiB
disk footprint148 B / obj

Apple M2 Max ยท 32 GB ยท SSD ยท JDK 17 ยท -Xmx8g. Durable single writes scale with concurrent writers, which share disk flushes. Full report โ†’

Documentation

Learn more

Community

Get involved