Skip to content

TidesDB Java API Reference

If you want to download the source of this document, you can find it here.


Getting Started

Prerequisites

You must have the TidesDB shared C library installed on your system. You can find the installation instructions here.

Requirements

  • Java 11 or higher
  • Maven 3.6+
  • TidesDB native library installed on the system

Building the JNI Library

Terminal window
cd src/main/c
cmake -S . -B build
cmake --build build
sudo cmake --install build

Adding to Your Project

Maven

<dependency>
<groupId>com.tidesdb</groupId>
<artifactId>tidesdb-java</artifactId>
<version>0.3.0</version>
</dependency>

Usage

Opening and Closing a Database

import com.tidesdb.*;
public class Example {
public static void main(String[] args) throws TidesDBException {
Config config = Config.builder("./mydb")
.numFlushThreads(2)
.numCompactionThreads(2)
.logLevel(LogLevel.INFO)
.blockCacheSize(64 * 1024 * 1024)
.maxOpenSSTables(256)
.build();
try (TidesDB db = TidesDB.open(config)) {
System.out.println("Database opened successfully");
}
}
}

Creating and Dropping Column Families

Column families are isolated key-value stores with independent configuration.

// Create with default configuration
ColumnFamilyConfig cfConfig = ColumnFamilyConfig.defaultConfig();
db.createColumnFamily("my_cf", cfConfig);
// Create with custom configuration
ColumnFamilyConfig customConfig = ColumnFamilyConfig.builder()
.writeBufferSize(128 * 1024 * 1024)
.levelSizeRatio(10)
.minLevels(5)
.compressionAlgorithm(CompressionAlgorithm.LZ4_COMPRESSION)
.enableBloomFilter(true)
.bloomFPR(0.01)
.enableBlockIndexes(true)
.syncMode(SyncMode.SYNC_INTERVAL)
.syncIntervalUs(128000)
.defaultIsolationLevel(IsolationLevel.READ_COMMITTED)
.build();
db.createColumnFamily("custom_cf", customConfig);
db.dropColumnFamily("my_cf");
String[] cfNames = db.listColumnFamilies();
for (String name : cfNames) {
System.out.println("Column family: " + name);
}

Working with Transactions

Writing Data

ColumnFamily cf = db.getColumnFamily("my_cf");
try (Transaction txn = db.beginTransaction()) {
txn.put(cf, "key".getBytes(), "value".getBytes());
txn.commit();
}

Writing with TTL

import java.time.Instant;
ColumnFamily cf = db.getColumnFamily("my_cf");
try (Transaction txn = db.beginTransaction()) {
long ttl = Instant.now().getEpochSecond() + 10;
txn.put(cf, "temp_key".getBytes(), "temp_value".getBytes(), ttl);
txn.commit();
}

TTL Examples

long ttl = -1;
long ttl = Instant.now().getEpochSecond() + 5 * 60;
long ttl = Instant.now().getEpochSecond() + 60 * 60;
long ttl = LocalDateTime.of(2026, 12, 31, 23, 59, 59)
.toEpochSecond(ZoneOffset.UTC);

Reading Data

ColumnFamily cf = db.getColumnFamily("my_cf");
try (Transaction txn = db.beginTransaction()) {
byte[] value = txn.get(cf, "key".getBytes());
System.out.println("Value: " + new String(value));
}

Deleting Data

ColumnFamily cf = db.getColumnFamily("my_cf");
try (Transaction txn = db.beginTransaction()) {
txn.delete(cf, "key".getBytes());
txn.commit();
}

Multi-Operation Transactions

ColumnFamily cf = db.getColumnFamily("my_cf");
try (Transaction txn = db.beginTransaction()) {
txn.put(cf, "key1".getBytes(), "value1".getBytes());
txn.put(cf, "key2".getBytes(), "value2".getBytes());
txn.delete(cf, "old_key".getBytes());
txn.commit();
} catch (TidesDBException e) {
throw e;
}

Iterating Over Data

Iterators provide efficient bidirectional traversal over key-value pairs.

Forward Iteration

ColumnFamily cf = db.getColumnFamily("my_cf");
try (Transaction txn = db.beginTransaction()) {
try (TidesDBIterator iter = txn.newIterator(cf)) {
iter.seekToFirst();
while (iter.isValid()) {
byte[] key = iter.key();
byte[] value = iter.value();
System.out.printf("Key: %s, Value: %s%n",
new String(key), new String(value));
iter.next();
}
}
}

Backward Iteration

ColumnFamily cf = db.getColumnFamily("my_cf");
try (Transaction txn = db.beginTransaction()) {
try (TidesDBIterator iter = txn.newIterator(cf)) {
iter.seekToLast();
while (iter.isValid()) {
byte[] key = iter.key();
byte[] value = iter.value();
System.out.printf("Key: %s, Value: %s%n",
new String(key), new String(value));
iter.prev();
}
}
}

Seeking to a Specific Key

try (TidesDBIterator iter = txn.newIterator(cf)) {
iter.seek("prefix".getBytes());
iter.seekForPrev("prefix".getBytes());
}

Getting Column Family Statistics

ColumnFamily cf = db.getColumnFamily("my_cf");
Stats stats = cf.getStats();
System.out.println("Number of levels: " + stats.getNumLevels());
System.out.println("Memtable size: " + stats.getMemtableSize());
System.out.println("Total keys: " + stats.getTotalKeys());
System.out.println("Total data size: " + stats.getTotalDataSize());
System.out.println("Average key size: " + stats.getAvgKeySize());
System.out.println("Average value size: " + stats.getAvgValueSize());
System.out.println("Read amplification: " + stats.getReadAmp());
System.out.println("Hit rate: " + stats.getHitRate());
if (stats.isUseBtree()) {
System.out.println("B+tree total nodes: " + stats.getBtreeTotalNodes());
System.out.println("B+tree max height: " + stats.getBtreeMaxHeight());
System.out.println("B+tree avg height: " + stats.getBtreeAvgHeight());
}
long[] levelSizes = stats.getLevelSizes();
int[] levelSSTables = stats.getLevelNumSSTables();
long[] levelKeyCounts = stats.getLevelKeyCounts();

Stats Fields

FieldTypeDescription
numLevelsintNumber of LSM levels
memtableSizelongCurrent memtable size in bytes
levelSizeslong[]Size of each level in bytes
levelNumSSTablesint[]Number of SSTables at each level
levelKeyCountslong[]Number of keys per level
totalKeyslongTotal keys across memtable and all SSTables
totalDataSizelongTotal data size (klog + vlog) in bytes
avgKeySizedoubleAverage key size in bytes
avgValueSizedoubleAverage value size in bytes
readAmpdoubleRead amplification (point lookup cost multiplier)
hitRatedoubleCache hit rate (0.0 if cache disabled)
useBtreebooleanWhether column family uses B+tree klog format
btreeTotalNodeslongTotal B+tree nodes (only if useBtree=true)
btreeMaxHeightintMaximum B+tree height (only if useBtree=true)
btreeAvgHeightdoubleAverage B+tree height (only if useBtree=true)
configColumnFamilyConfigColumn family configuration

Getting Cache Statistics

CacheStats cacheStats = db.getCacheStats();
System.out.println("Cache enabled: " + cacheStats.isEnabled());
System.out.println("Total entries: " + cacheStats.getTotalEntries());
System.out.println("Hit rate: " + cacheStats.getHitRate());

Manual Compaction and Flush

ColumnFamily cf = db.getColumnFamily("my_cf");
cf.compact();
cf.flushMemtable();
boolean flushing = cf.isFlushing();
boolean compacting = cf.isCompacting();

Updating Runtime Configuration

Update runtime-safe configuration settings for a column family:

ColumnFamily cf = db.getColumnFamily("my_cf");
ColumnFamilyConfig newConfig = ColumnFamilyConfig.builder()
.writeBufferSize(256 * 1024 * 1024)
.skipListMaxLevel(16)
.bloomFPR(0.001)
.syncMode(SyncMode.SYNC_INTERVAL)
.syncIntervalUs(100000)
.build();
cf.updateRuntimeConfig(newConfig, true);

Updatable settings (safe to change at runtime):

  • writeBufferSize · Memtable flush threshold
  • skipListMaxLevel · Skip list level for new memtables
  • skipListProbability · Skip list probability for new memtables
  • bloomFPR · False positive rate for new SSTables
  • indexSampleRatio · Index sampling ratio for new SSTables
  • syncMode · Durability mode
  • syncIntervalUs · Sync interval in microseconds

Database Backup

Create an on-disk snapshot without blocking normal reads/writes:

db.backup("./mydb_backup");

Database Checkpoint

Create a lightweight, near-instant snapshot of an open database using hard links instead of copying SSTable data:

db.checkpoint("./mydb_checkpoint");

Checkpoint vs Backup

backup()checkpoint()
SpeedCopies every SSTable byte-by-byteNear-instant (hard links, O(1) per file)
Disk usageFull independent copyNo extra disk until compaction removes old SSTables
PortabilityCan be moved to another filesystem or machineSame filesystem only (hard link requirement)
Use caseArchival, disaster recovery, remote shippingFast local snapshots, point-in-time reads, streaming backups

Behavior

  • Requires the directory to be non-existent or empty
  • For each column family: flushes the active memtable, halts compactions, hard links all SSTable files, copies small metadata files, then resumes compactions
  • Falls back to file copy if hard linking fails (e.g., cross-filesystem)
  • Database stays open and usable during checkpoint
  • The checkpoint can be opened as a normal TidesDB database with TidesDB.open()

Renaming Column Families

Atomically rename a column family:

db.renameColumnFamily("old_name", "new_name");

Cloning Column Families

Create a complete copy of an existing column family with a new name. The clone is completely independent; modifications to one do not affect the other.

db.cloneColumnFamily("source_cf", "cloned_cf");
ColumnFamily original = db.getColumnFamily("source_cf");
ColumnFamily clone = db.getColumnFamily("cloned_cf");

Use cases

  • Testing · Create a copy of production data for testing without affecting the original
  • Branching · Create a snapshot of data before making experimental changes
  • Migration · Clone data before schema or configuration changes
  • Backup verification · Clone and verify data integrity without modifying the source

B+tree KLog Format (Optional)

Column families can optionally use a B+tree structure for the key log instead of the default block-based format. The B+tree klog format offers faster point lookups through O(log N) tree traversal.

ColumnFamilyConfig btreeConfig = ColumnFamilyConfig.builder()
.writeBufferSize(128 * 1024 * 1024)
.compressionAlgorithm(CompressionAlgorithm.LZ4_COMPRESSION)
.enableBloomFilter(true)
.useBtree(true)
.build();
db.createColumnFamily("btree_cf", btreeConfig);
ColumnFamily cf = db.getColumnFamily("btree_cf");
try (Transaction txn = db.beginTransaction()) {
txn.put(cf, "key".getBytes(), "value".getBytes());
txn.commit();
}
Stats stats = cf.getStats();
if (stats.isUseBtree()) {
System.out.println("B+tree nodes: " + stats.getBtreeTotalNodes());
System.out.println("B+tree max height: " + stats.getBtreeMaxHeight());
System.out.println("B+tree avg height: " + stats.getBtreeAvgHeight());
}

When to use B+tree klog format

  • Read-heavy workloads with frequent point lookups
  • Workloads where read latency is more important than write throughput
  • Large SSTables where block scanning becomes expensive

Tradeoffs

  • Slightly higher write amplification during flush
  • Larger metadata overhead per node
  • Block-based format may be faster for sequential scans

Transaction Isolation Levels

try (Transaction txn = db.beginTransaction(IsolationLevel.SERIALIZABLE)) {
txn.commit();
}

Available Isolation Levels

  • READ_UNCOMMITTED · Sees all data including uncommitted changes
  • READ_COMMITTED · Sees only committed data (default)
  • REPEATABLE_READ · Consistent snapshot, phantom reads possible
  • SNAPSHOT · Write-write conflict detection
  • SERIALIZABLE · Full read-write conflict detection (SSI)

Savepoints

Savepoints allow partial rollback within a transaction:

try (Transaction txn = db.beginTransaction()) {
txn.put(cf, "key1".getBytes(), "value1".getBytes());
txn.savepoint("sp1");
txn.put(cf, "key2".getBytes(), "value2".getBytes());
txn.rollbackToSavepoint("sp1");
txn.commit();
}

Savepoint API

  • savepoint(name) · Create a savepoint
  • rollbackToSavepoint(name) · Rollback to savepoint
  • releaseSavepoint(name) · Release savepoint without rolling back

Transaction Reset

reset resets a committed or aborted transaction for reuse with a new isolation level. This avoids the overhead of freeing and reallocating transaction resources in hot loops.

ColumnFamily cf = db.getColumnFamily("my_cf");
Transaction txn = db.beginTransaction();
txn.put(cf, "key1".getBytes(), "value1".getBytes());
txn.commit();
txn.reset(IsolationLevel.READ_COMMITTED);
txn.put(cf, "key2".getBytes(), "value2".getBytes());
txn.commit();
txn.free();

Behavior

  • The transaction must be committed or aborted before reset; resetting an active transaction throws TidesDBException
  • Internal buffers are retained to avoid reallocation
  • A fresh transaction ID and snapshot sequence are assigned based on the new isolation level
  • The isolation level can be changed on each reset (e.g., READ_COMMITTED to REPEATABLE_READ)

When to use

  • Batch processing · Reuse a single transaction across many commit cycles in a loop
  • Connection pooling · Reset a transaction for a new request without reallocation
  • High-throughput ingestion · Reduce allocation overhead in tight write loops

Reset vs Free + Begin

For a single transaction, reset is functionally equivalent to calling free followed by beginTransaction. The difference is performance: reset retains allocated buffers and avoids repeated allocation overhead. This matters most in loops that commit and restart thousands of transactions.

Configuration Options

Database Configuration

OptionTypeDefaultDescription
dbPathString-Path to the database directory
numFlushThreadsint2Number of flush threads
numCompactionThreadsint2Number of compaction threads
logLevelLogLevelINFOLogging level
blockCacheSizelong64MBBlock cache size in bytes
maxOpenSSTableslong256Maximum open SSTable files
logToFilebooleanfalseWrite logs to file instead of stderr
logTruncationAtlong24MBLog file truncation size (0 = no truncation)

Column Family Configuration

OptionTypeDefaultDescription
writeBufferSizelong128MBMemtable flush threshold
levelSizeRatiolong10Level size multiplier
minLevelsint5Minimum LSM levels
dividingLevelOffsetint2Compaction dividing level offset
klogValueThresholdlong512Values > threshold go to vlog
compressionAlgorithmCompressionAlgorithmLZ4_COMPRESSIONCompression algorithm
enableBloomFilterbooleantrueEnable bloom filters
bloomFPRdouble0.01Bloom filter false positive rate (1%)
enableBlockIndexesbooleantrueEnable compact block indexes
indexSampleRatioint1Sample every block for index
blockIndexPrefixLenint16Block index prefix length
syncModeSyncModeSYNC_FULLSync mode for durability
syncIntervalUslong1000000Sync interval (1 second, for SYNC_INTERVAL)
comparatorNameString""Custom comparator name (empty = memcmp)
skipListMaxLevelint12Skip list max level
skipListProbabilityfloat0.25Skip list probability
defaultIsolationLevelIsolationLevelREAD_COMMITTEDDefault transaction isolation
minDiskSpacelong100MBMinimum disk space required
l1FileCountTriggerint4L1 file count trigger for compaction
l0QueueStallThresholdint20L0 queue stall threshold
useBtreebooleanfalseUse B+tree format for klog (faster point lookups)

Compression Algorithms

AlgorithmValueDescription
NO_COMPRESSION0No compression
SNAPPY_COMPRESSION1Snappy compression
LZ4_COMPRESSION2LZ4 standard compression (default)
ZSTD_COMPRESSION3Zstandard compression (best ratio)
LZ4_FAST_COMPRESSION4LZ4 fast mode (higher throughput)

Sync Modes

ModeDescription
SYNC_NONENo explicit sync, relies on OS page cache (fastest)
SYNC_FULLFsync on every write (most durable)
SYNC_INTERVALPeriodic background syncing at configurable intervals

Error Codes

CodeValueDescription
ERR_SUCCESS0Operation completed successfully
ERR_MEMORY-1Memory allocation failed
ERR_INVALID_ARGS-2Invalid arguments passed
ERR_NOT_FOUND-3Key not found
ERR_IO-4I/O operation failed
ERR_CORRUPTION-5Data corruption detected
ERR_EXISTS-6Resource already exists
ERR_CONFLICT-7Transaction conflict detected
ERR_TOO_LARGE-8Key or value size exceeds maximum
ERR_MEMORY_LIMIT-9Memory limit exceeded
ERR_INVALID_DB-10Database handle is invalid
ERR_UNKNOWN-11Unknown error
ERR_LOCKED-12Database is locked

Testing

Terminal window
# Run all tests
mvn test
# Run specific test
mvn test -Dtest=TidesDBTest#testOpenClose
# Run with verbose output
mvn test -X

Building from Source

Terminal window
# Clone the repository
git clone https://github.com/tidesdb/tidesdb-java.git
cd tidesdb-java
# Build the JNI library
cd src/main/c
cmake -S . -B build
cmake --build build
sudo cmake --install build
cd ../../..
# Build the Java package
mvn clean package
# Install to local Maven repository
mvn install