or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

examples

edge-cases.mdreal-world-scenarios.md
index.md
tile.json

java-api.mddocs/reference/

FoundationDB Java API Reference

A complete reference for the FoundationDB Java client library, providing a CompletableFuture-based API for ACID transactional operations on a distributed key-value store.

Package: com.apple.foundationdb Version: 7.4.5 Architecture: Wraps the C API with idiomatic Java interfaces Async Model: CompletableFuture-based asynchronous operations

Package Information

Core Package

import com.apple.foundationdb.*;

Core classes for database connectivity, transactions, and data operations.

Async Utilities

import com.apple.foundationdb.async.*;

Utilities for asynchronous iteration and future composition.

Tuple Layer

import com.apple.foundationdb.tuple.*;

Type-safe encoding/decoding of structured keys using tuples.

Subspace

import com.apple.foundationdb.subspace.*;

Key prefixing and namespace isolation.

Directory Layer

import com.apple.foundationdb.directory.*;

Dynamic hierarchical prefix allocation and management.

Initialization and Connection

Entry Point: FDB Class

The FDB class is the entry point for all FoundationDB operations in Java.

// Static methods
public static FDB selectAPIVersion(int version) throws FDBException

// Instance methods (after selectAPIVersion)
public static FDB instance() throws FDBException
public Database open() throws FDBException
public Database open(String clusterFilePath) throws FDBException
public void startNetwork() throws FDBException
public void stopNetwork() throws FDBException
public NetworkOptions options()

Initialization Pattern:

// 1. Select API version (required first call)
FDB fdb = FDB.selectAPIVersion(740);

// 2. Open database (network thread starts automatically)
Database db = fdb.open();

// Note: Network thread management is automatic in most cases
// Only call startNetwork() explicitly if you need manual control

Example: Basic Initialization

import com.apple.foundationdb.*;

public class Example {
    public static void main(String[] args) {
        // Select API version
        FDB fdb = FDB.selectAPIVersion(740);

        // Open database connection
        try (Database db = fdb.open()) {
            // Use database...
            byte[] value = db.run(tr -> {
                return tr.get("mykey".getBytes()).join();
            });

            if (value != null) {
                System.out.println("Value: " + new String(value));
            }
        }
    }
}

Database Interface

The Database interface represents a connection to FoundationDB and provides transaction management.

Transaction Management

public Transaction createTransaction()
public Transaction createTransaction(Executor e)

Description: Create a new transaction for manual control.

Example:

try (Transaction tr = db.createTransaction()) {
    tr.set("key".getBytes(), "value".getBytes());
    tr.commit().join();
}

Transactional Execution (Recommended)

public <T> T run(Function<? super Transaction, T> retryable)
public <T> T run(Function<? super Transaction, T> retryable, Executor e)
public <T> T read(Function<? super ReadTransaction, T> retryable)
public <T> T read(Function<? super ReadTransaction, T> retryable, Executor e)

Description: Execute transactional functions with automatic retry logic. The function will be retried automatically on retryable errors.

Parameters:

  • retryable: Function that receives a transaction and returns a result
  • e: Custom executor for async operations (optional)

Returns: Result of type T from the function

Example: Write Transaction

db.run(tr -> {
    tr.set("user:1001:name".getBytes(), "Alice".getBytes());
    tr.set("user:1001:email".getBytes(), "alice@example.com".getBytes());
    return null; // Commit is automatic
});

Example: Read Transaction

String name = db.read(tr -> {
    byte[] value = tr.get("user:1001:name".getBytes()).join();
    return value != null ? new String(value) : null;
});

Example: Read-Write Transaction

long newBalance = db.run(tr -> {
    byte[] balanceBytes = tr.get("account:1001:balance".getBytes()).join();
    long balance = balanceBytes != null ?
        ByteBuffer.wrap(balanceBytes).getLong() : 0L;

    long newBal = balance + 100;
    ByteBuffer buf = ByteBuffer.allocate(8);
    buf.putLong(newBal);
    tr.set("account:1001:balance".getBytes(), buf.array());

    return newBal;
});

Tenant Operations

public Tenant openTenant(byte[] tenantName)
public Tenant openTenant(byte[] tenantName, Executor e)

Description: Open a tenant for multi-tenancy support. Tenants provide isolated namespaces within a single database.

Example:

Tenant tenant = db.openTenant("company_a".getBytes());

tenant.run(tr -> {
    tr.set("data".getBytes(), "value".getBytes());
    return null;
});

Configuration

public DatabaseOptions options()

Description: Get database options interface for configuration.

Tenant Interface

The Tenant interface provides the same transactional capabilities as Database, but scoped to a tenant namespace.

Transaction Management

public Transaction createTransaction()
public Transaction createTransaction(Executor e)

Transactional Execution

public <T> T run(Function<? super Transaction, T> retryable)
public <T> T run(Function<? super Transaction, T> retryable, Executor e)
public <T> T read(Function<? super ReadTransaction, T> retryable)
public <T> T read(Function<? super ReadTransaction, T> retryable, Executor e)

Example: Tenant Isolation

Tenant tenantA = db.openTenant("tenant_a".getBytes());
Tenant tenantB = db.openTenant("tenant_b".getBytes());

// Write to tenant A
tenantA.run(tr -> {
    tr.set("config".getBytes(), "valueA".getBytes());
    return null;
});

// Write to tenant B (isolated from A)
tenantB.run(tr -> {
    tr.set("config".getBytes(), "valueB".getBytes());
    return null;
});

// Each tenant has its own namespace

TenantManagement Utility Class

The TenantManagement class provides static utility methods for managing tenants in a FoundationDB database.

Package: com.apple.foundationdb.TenantManagement

Create Tenant

public static void createTenant(Transaction tr, byte[] tenantName)
public static void createTenant(Transaction tr, Tuple tenantName)
public static CompletableFuture<Void> createTenant(Database db, byte[] tenantName)
public static CompletableFuture<Void> createTenant(Database db, Tuple tenantName)

Create a new tenant with the specified name.

Parameters:

  • tr / db: Transaction or Database context
  • tenantName: Tenant name as bytes or Tuple

Returns: CompletableFuture<Void> when using Database (void for Transaction)

Throws: FDBException if tenant already exists

Example:

import com.apple.foundationdb.TenantManagement;
import com.apple.foundationdb.tuple.Tuple;

// Create tenant using database
TenantManagement.createTenant(db, "my_tenant".getBytes()).join();

// Or using Tuple
TenantManagement.createTenant(db, Tuple.from("my_tenant")).join();

// Or within a transaction
db.run(tr -> {
    TenantManagement.createTenant(tr, "another_tenant".getBytes());
    return null;
});

Delete Tenant

public static void deleteTenant(Transaction tr, byte[] tenantName)
public static void deleteTenant(Transaction tr, Tuple tenantName)
public static CompletableFuture<Void> deleteTenant(Database db, byte[] tenantName)
public static CompletableFuture<Void> deleteTenant(Database db, Tuple tenantName)

Delete a tenant and all its data.

Parameters:

  • tr / db: Transaction or Database context
  • tenantName: Tenant name as bytes or Tuple

Returns: CompletableFuture<Void> when using Database (void for Transaction)

Throws: FDBException if tenant does not exist or is not empty

Example:

// Delete tenant
TenantManagement.deleteTenant(db, "my_tenant".getBytes()).join();

// Or using Tuple
TenantManagement.deleteTenant(db, Tuple.from("my_tenant")).join();

List Tenants

public static CloseableAsyncIterator<KeyValue> listTenants(
    Database db,
    byte[] begin,
    byte[] end,
    int limit
)
public static CloseableAsyncIterator<KeyValue> listTenants(
    Database db,
    Tuple begin,
    Tuple end,
    int limit
)

List all tenants in a given range.

Parameters:

  • db: Database context
  • begin: Start of tenant name range (inclusive)
  • end: End of tenant name range (exclusive)
  • limit: Maximum number of tenants to return

Returns: CloseableAsyncIterator<KeyValue> where keys are tenant names

Example:

// List all tenants
try (CloseableAsyncIterator<KeyValue> tenants =
        TenantManagement.listTenants(db, new byte[0], new byte[]{(byte)0xFF}, 1000)) {

    tenants.forEachRemaining(kv -> {
        String tenantName = new String(kv.getKey());
        System.out.println("Tenant: " + tenantName);
    });
}

// List tenants with prefix using Tuple
try (CloseableAsyncIterator<KeyValue> tenants =
        TenantManagement.listTenants(
            db,
            Tuple.from("prod"),
            Tuple.from("prod" + "\uffff"),
            100)) {
    // Process tenants...
}

LocalityUtil Utility Class

The LocalityUtil class provides static utility methods for querying data locality and distribution information.

Package: com.apple.foundationdb.LocalityUtil

Get Boundary Keys

public static CloseableAsyncIterator<byte[]> getBoundaryKeys(
    Database db,
    byte[] begin,
    byte[] end
)
public static CloseableAsyncIterator<byte[]> getBoundaryKeys(
    Transaction tr,
    byte[] begin,
    byte[] end
)

Get the keys that define shard boundaries within a range. Useful for parallel processing and understanding data distribution.

Parameters:

  • db / tr: Database or Transaction context
  • begin: Start key (inclusive)
  • end: End key (exclusive)

Returns: CloseableAsyncIterator<byte[]> of boundary keys

Example:

import com.apple.foundationdb.LocalityUtil;

// Get shard boundaries for a range
try (CloseableAsyncIterator<byte[]> boundaries =
        LocalityUtil.getBoundaryKeys(db, "users/".getBytes(), "users0".getBytes())) {

    List<byte[]> boundaryList = new ArrayList<>();
    boundaries.forEachRemaining(boundaryList::add);

    System.out.println("Found " + boundaryList.size() + " shard boundaries");

    // Can use these boundaries for parallel processing
    for (int i = 0; i < boundaryList.size() - 1; i++) {
        byte[] rangeStart = boundaryList.get(i);
        byte[] rangeEnd = boundaryList.get(i + 1);
        // Process each shard in parallel...
    }
}

Get Addresses for Key

public static CompletableFuture<String[]> getAddressesForKey(
    Transaction tr,
    byte[] key
)

Get the network addresses of all storage servers that hold replicas of a specific key.

Parameters:

  • tr: Transaction context (must be a read transaction)
  • key: The key to query

Returns: CompletableFuture<String[]> of server addresses in "IP:PORT" format

Example:

db.read(tr -> {
    String[] addresses = LocalityUtil.getAddressesForKey(tr, "mykey".getBytes()).join();

    System.out.println("Key is stored on " + addresses.length + " replicas:");
    for (String addr : addresses) {
        System.out.println("  - " + addr);
    }

    return null;
});

Use Cases:

  • Debugging: Understanding data placement
  • Performance optimization: Locality-aware application design
  • Monitoring: Tracking replica distribution

ReadTransaction Interface

The ReadTransaction interface provides read-only operations. This is the base interface for Transaction.

Single Key Read

public CompletableFuture<byte[]> get(byte[] key)

Description: Read a single key's value asynchronously.

Parameters:

  • key: Key to read (byte array)

Returns: CompletableFuture<byte[]> that completes with the value, or null if the key doesn't exist

Example:

CompletableFuture<byte[]> future = tr.get("mykey".getBytes());
byte[] value = future.join(); // Block until ready

if (value != null) {
    System.out.println("Found: " + new String(value));
} else {
    System.out.println("Key not found");
}

Key Selector Resolution

public CompletableFuture<byte[]> getKey(KeySelector selector)

Description: Resolve a key selector to an actual key asynchronously.

Parameters:

  • selector: KeySelector specifying the key to find

Returns: CompletableFuture<byte[]> that completes with the resolved key

Example:

// Find first key >= "user:"
KeySelector selector = KeySelector.firstGreaterOrEqual("user:".getBytes());
byte[] key = tr.getKey(selector).join();

Range Reads

// Range with KeySelectors
public AsyncIterable<KeyValue> getRange(KeySelector begin, KeySelector end)
public AsyncIterable<KeyValue> getRange(KeySelector begin, KeySelector end,
                                         int limit)
public AsyncIterable<KeyValue> getRange(KeySelector begin, KeySelector end,
                                         int limit, boolean reverse)
public AsyncIterable<KeyValue> getRange(KeySelector begin, KeySelector end,
                                         int limit, boolean reverse,
                                         StreamingMode mode)

// Range with byte[] keys
public AsyncIterable<KeyValue> getRange(byte[] begin, byte[] end)
public AsyncIterable<KeyValue> getRange(byte[] begin, byte[] end, int limit)
public AsyncIterable<KeyValue> getRange(byte[] begin, byte[] end,
                                         int limit, boolean reverse)
public AsyncIterable<KeyValue> getRange(byte[] begin, byte[] end,
                                         int limit, boolean reverse,
                                         StreamingMode mode)

// Range with Range object
public AsyncIterable<KeyValue> getRange(Range range)
public AsyncIterable<KeyValue> getRange(Range range, int limit)
public AsyncIterable<KeyValue> getRange(Range range, int limit, boolean reverse)
public AsyncIterable<KeyValue> getRange(Range range, int limit, boolean reverse,
                                         StreamingMode mode)

Description: Read a range of key-value pairs. Returns an asynchronous iterator that lazily fetches results.

Parameters:

  • begin: Start of range (inclusive or exclusive based on KeySelector)
  • end: End of range (exclusive)
  • limit: Maximum number of key-value pairs to return (0 = no limit)
  • reverse: If true, return results in reverse order
  • mode: Streaming mode controlling fetch behavior

Returns: AsyncIterable<KeyValue> for iterating over results

Example: Basic Range Read

AsyncIterable<KeyValue> range = tr.getRange(
    "user:".getBytes(),
    "user;".getBytes() // ';' is one byte after ':'
);

// Collect all results
List<KeyValue> results = range.asList().join();

for (KeyValue kv : results) {
    System.out.println(new String(kv.getKey()) + " = " +
                      new String(kv.getValue()));
}

Example: Limited Range Read

// Get first 100 users
AsyncIterable<KeyValue> range = tr.getRange(
    "user:".getBytes(),
    "user;".getBytes(),
    100 // limit
);

Example: Reverse Range Read

// Get last 10 users
AsyncIterable<KeyValue> range = tr.getRange(
    "user:".getBytes(),
    "user;".getBytes(),
    10,    // limit
    true   // reverse
);

Example: Async Iteration

AsyncIterable<KeyValue> range = tr.getRange("user:".getBytes(),
                                            "user;".getBytes());

AsyncIterator<KeyValue> iterator = range.iterator();

while (iterator.onHasNext().join()) {
    KeyValue kv = iterator.next();
    processKeyValue(kv);
}

Mapped Range Read

public AsyncIterable<MappedKeyValue> getMappedRange(KeySelector begin,
                                                      KeySelector end,
                                                      byte[] mapper,
                                                      int limit,
                                                      boolean reverse,
                                                      StreamingMode mode)

Description: Read a range with server-side mapping/filtering.

Version Operations

public CompletableFuture<Long> getReadVersion()
public void setReadVersion(long version)

Description: Get or set the transaction's read version for consistent reads.

Example: Read at Specific Version

// Get current read version
long version = tr.getReadVersion().join();
System.out.println("Reading at version: " + version);

// Create new transaction at same version
try (Transaction tr2 = db.createTransaction()) {
    tr2.setReadVersion(version);
    // This read will see the same version as tr
    byte[] value = tr2.get("key".getBytes()).join();
}

Size Estimation

public CompletableFuture<Long> getEstimatedRangeSizeBytes(byte[] begin, byte[] end)
public CompletableFuture<Long> getEstimatedRangeSizeBytes(Range range)

Description: Estimate the size in bytes of a key range.

Returns: CompletableFuture<Long> with estimated size in bytes

Example:

long estimatedSize = tr.getEstimatedRangeSizeBytes(
    "user:".getBytes(),
    "user;".getBytes()
).join();

System.out.println("Estimated size: " + estimatedSize + " bytes");

Split Points

public CompletableFuture<KeyArrayResult> getRangeSplitPoints(byte[] begin, byte[] end,
                                                               long chunkSize)
public CompletableFuture<KeyArrayResult> getRangeSplitPoints(Range range, long chunkSize)

Description: Get suggested split points for parallel processing of a range.

Parameters:

  • begin, end: Range boundaries
  • chunkSize: Desired chunk size in bytes

Returns: CompletableFuture<KeyArrayResult> with split point keys

Example: Parallel Range Processing

// Get split points for 10MB chunks
KeyArrayResult splitPoints = tr.getRangeSplitPoints(
    "data:".getBytes(),
    "data;".getBytes(),
    10_000_000 // 10MB
).join();

// Process each chunk in parallel
for (int i = 0; i < splitPoints.size() + 1; i++) {
    byte[] begin = (i == 0) ? "data:".getBytes() : splitPoints.get(i - 1);
    byte[] end = (i == splitPoints.size()) ? "data;".getBytes() : splitPoints.get(i);

    processChunkInParallel(begin, end);
}

Snapshot Access

public ReadTransaction snapshot()

Description: Get a snapshot view of the transaction for reads without adding read conflicts.

Returns: ReadTransaction interface for snapshot reads

Example:

// Normal read - adds conflict range
byte[] value1 = tr.get("counter".getBytes()).join();

// Snapshot read - no conflict range
byte[] value2 = tr.snapshot().get("counter".getBytes()).join();

Configuration

public TransactionOptions options()

Description: Get transaction options interface for configuration.

Blob Granules

public CompletableFuture<KeyRangeArrayResult> getBlobGranuleRanges(byte[] begin, byte[] end,
                                                                     int rowLimit)

Description: Get blob granule ranges for time-travel queries (requires blob granule configuration).

Transaction Interface

The Transaction interface extends ReadTransaction with write operations and transaction control.

Write Operations

public void set(byte[] key, byte[] value)

Description: Set a key to a value. The write is buffered until commit.

Parameters:

  • key: Key to write (byte array)
  • value: Value to write (byte array)

Example:

tr.set("user:1001:name".getBytes(), "Alice".getBytes());
tr.set("user:1001:age".getBytes(), "30".getBytes());
public void clear(byte[] key)

Description: Delete a single key.

Example:

tr.clear("user:1001:name".getBytes());
public void clear(byte[] begin, byte[] end)
public void clear(Range range)

Description: Delete all keys in a range [begin, end).

Example:

// Delete all keys starting with "temp:"
tr.clear("temp:".getBytes(), "temp;".getBytes());

// Or using Range object
tr.clear(Range.startsWith("temp:".getBytes()));

Atomic Operations

public void mutate(MutationType opType, byte[] key, byte[] param)

Description: Perform an atomic mutation on a key. The operation is applied atomically at commit time.

Parameters:

  • opType: Type of atomic operation (from MutationType enum)
  • key: Key to mutate
  • param: Parameter for the operation (interpretation depends on opType)

Common MutationType Values:

  • ADD: Add integer value (little-endian encoded)
  • BIT_AND: Bitwise AND
  • BIT_OR: Bitwise OR
  • BIT_XOR: Bitwise XOR
  • MAX: Take maximum value (lexicographic)
  • MIN: Take minimum value (lexicographic)
  • BYTE_MAX: Take maximum value (bytewise comparison)
  • BYTE_MIN: Take minimum value (bytewise comparison)
  • COMPARE_AND_CLEAR: Clear key if value matches param
  • APPEND_IF_FITS: Append if result fits in value size limit
  • SET_VERSIONSTAMPED_KEY: Set with versionstamp in key
  • SET_VERSIONSTAMPED_VALUE: Set with versionstamp in value

Example: Atomic Counter

// Increment counter by 1
ByteBuffer buf = ByteBuffer.allocate(8);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.putLong(1L);

tr.mutate(MutationType.ADD, "counter".getBytes(), buf.array());

Example: Atomic Max

// Store the maximum of current value and new value
tr.mutate(MutationType.MAX, "highscore".getBytes(), "9500".getBytes());

Example: Versionstamp Value

// Incomplete versionstamp (10 bytes of 0xFF followed by user version)
byte[] incomplete = new byte[14];
Arrays.fill(incomplete, 0, 10, (byte) 0xFF);
ByteBuffer.wrap(incomplete).putInt(10, 0); // user version = 0

tr.mutate(MutationType.SET_VERSIONSTAMPED_VALUE,
         "versioned_key".getBytes(),
         incomplete);

// After commit, the 0xFF bytes will be replaced with the commit version

Transaction Control

public CompletableFuture<Void> commit()

Description: Commit the transaction asynchronously. All buffered writes are applied atomically.

Returns: CompletableFuture<Void> that completes when commit succeeds or fails

Example: Manual Transaction

try (Transaction tr = db.createTransaction()) {
    try {
        tr.set("key".getBytes(), "value".getBytes());
        tr.commit().join();
    } catch (Exception e) {
        // Handle commit error
        System.err.println("Commit failed: " + e.getMessage());
    }
}
public CompletableFuture<Transaction> onError(Throwable t)

Description: Handle an error with automatic retry logic. Returns a future that completes when the transaction is ready to retry, or fails if the error is not retryable.

Parameters:

  • t: The error/exception to handle

Returns: CompletableFuture<Transaction> that completes with the same transaction when ready to retry

Example: Manual Retry Loop

try (Transaction tr = db.createTransaction()) {
    while (true) {
        try {
            tr.set("key".getBytes(), "value".getBytes());
            tr.commit().join();
            break; // Success
        } catch (Throwable e) {
            try {
                tr.onError(e).join(); // Wait for retry
            } catch (Exception retryError) {
                // Non-retryable error
                throw retryError;
            }
        }
    }
}
public void cancel()

Description: Cancel the transaction. All outstanding reads will fail.

Watch

public CompletableFuture<Void> watch(byte[] key)

Description: Watch for changes to a key. The returned future completes when the key is modified by another transaction.

Parameters:

  • key: Key to watch

Returns: CompletableFuture<Void> that completes when the key changes

Important: The watch must be committed for it to become active.

Example: Wait for Key Change

CompletableFuture<Void> watchFuture = db.run(tr -> {
    return tr.watch("config".getBytes());
});

System.out.println("Waiting for config to change...");
watchFuture.join(); // Blocks until config changes
System.out.println("Config changed!");

Example: Reactive Processing

CompletableFuture<Void> watchFuture = db.run(tr -> {
    return tr.watch("queue:ready".getBytes());
});

watchFuture.thenRun(() -> {
    System.out.println("Queue is ready, processing...");
    processQueue();
});

Version Operations

public Long getCommittedVersion()

Description: Get the version at which the transaction was committed. Only valid after successful commit.

Returns: Commit version as Long (boxed)

Example:

long commitVersion = db.run(tr -> {
    tr.set("key".getBytes(), "value".getBytes());
    // After successful commit (implicit in run()):
    return tr.getCommittedVersion();
});

System.out.println("Committed at version: " + commitVersion);
public CompletableFuture<byte[]> getVersionstamp()

Description: Get the versionstamp for this transaction. Only valid after successful commit. Returns a 10-byte versionstamp containing the commit version.

Returns: CompletableFuture<byte[]> with 10-byte versionstamp

Example:

byte[] versionstamp = db.run(tr -> {
    tr.set("key".getBytes(), "value".getBytes());
    // Get versionstamp after commit
    return tr.getVersionstamp().join();
});

// Versionstamp is 10 bytes: 8 bytes version + 2 bytes batch order
System.out.println("Versionstamp: " + bytesToHex(versionstamp));
public CompletableFuture<Long> getApproximateSize()

Description: Get the approximate transaction size in bytes.

Returns: CompletableFuture<Long> with size in bytes

Example:

long size = tr.getApproximateSize().join();
if (size > 10_000_000) {
    System.err.println("Transaction too large: " + size + " bytes");
}

Conflict Ranges

public void addReadConflictRange(byte[] begin, byte[] end)
public void addReadConflictKey(byte[] key)

Description: Manually add a read conflict range or key. The transaction will conflict with any transaction that writes to this range.

Example:

// Add read conflict without actually reading
tr.addReadConflictRange("data:".getBytes(), "data;".getBytes());
public void addWriteConflictRange(byte[] begin, byte[] end)
public void addWriteConflictKey(byte[] key)

Description: Manually add a write conflict range or key. Other transactions that read this range will conflict with this transaction.

Example:

// Add write conflict without actually writing
tr.addWriteConflictRange("index:".getBytes(), "index;".getBytes());

Data Types

KeyValue Class

Represents a key-value pair returned from range reads.

public byte[] getKey()
public byte[] getValue()

Example:

AsyncIterable<KeyValue> range = tr.getRange("user:".getBytes(),
                                            "user;".getBytes());

for (KeyValue kv : range.asList().join()) {
    String key = new String(kv.getKey());
    String value = new String(kv.getValue());
    System.out.println(key + " = " + value);
}

KeySelector Class

Represents a key selector for range boundaries.

// Constructor
public KeySelector(byte[] key, boolean orEqual, int offset)

// Factory methods
public static KeySelector firstGreaterThan(byte[] key)
public static KeySelector firstGreaterOrEqual(byte[] key)
public static KeySelector lastLessThan(byte[] key)
public static KeySelector lastLessOrEqual(byte[] key)

// Offset adjustment
public KeySelector add(int offset)

Description: A KeySelector identifies a key by reference to another key:

  • key: Reference key
  • orEqual: Whether to include the reference key
  • offset: Number of keys to skip forward (positive) or backward (negative)

Example: Key Selector Usage

// Get first key >= "user:1000"
KeySelector start = KeySelector.firstGreaterOrEqual("user:1000".getBytes());

// Get first key > "user:2000"
KeySelector end = KeySelector.firstGreaterThan("user:2000".getBytes());

AsyncIterable<KeyValue> range = tr.getRange(start, end);

Example: Offset Usage

// Get the 5th key after "user:1000"
KeySelector selector = KeySelector.firstGreaterOrEqual("user:1000".getBytes())
                                  .add(5);

byte[] key = tr.getKey(selector).join();

Range Class

Represents a key range.

// Constructor
public Range(byte[] begin, byte[] end)

// Factory method
public static Range startsWith(byte[] prefix)

Example: Range Creation

// Simple range
Range range1 = new Range("user:".getBytes(), "user;".getBytes());

// Range with prefix
Range range2 = Range.startsWith("user:".getBytes());

// Use in getRange
AsyncIterable<KeyValue> results = tr.getRange(range1);

StreamingMode Enum

Controls how range reads fetch data from the server.

public enum StreamingMode {
    ITERATOR,    // Balanced iteration (default)
    WANT_ALL,    // Fetch entire range
    EXACT,       // Fetch exactly the limit specified
    SMALL,       // Small data size hint
    MEDIUM,      // Medium data size hint
    LARGE,       // Large data size hint
    SERIAL       // Minimize latency
}

Description:

  • ITERATOR: Best for most cases - balances latency and bandwidth
  • WANT_ALL: Fetch all data at once (good for small ranges)
  • EXACT: Fetch exactly the specified limit
  • SMALL/MEDIUM/LARGE: Hints about expected data size
  • SERIAL: Minimize latency at the cost of bandwidth

Example:

// Fetch all data at once
AsyncIterable<KeyValue> all = tr.getRange(
    "small_range:".getBytes(),
    "small_range;".getBytes(),
    0,
    false,
    StreamingMode.WANT_ALL
);

// Balanced iteration (default)
AsyncIterable<KeyValue> balanced = tr.getRange(
    "large_range:".getBytes(),
    "large_range;".getBytes(),
    0,
    false,
    StreamingMode.ITERATOR
);

MutationType Enum

Defines atomic mutation operations.

public enum MutationType {
    ADD,                        // Atomic integer addition
    BIT_AND,                    // Bitwise AND
    BIT_OR,                     // Bitwise OR
    BIT_XOR,                    // Bitwise XOR
    MAX,                        // Lexicographic maximum
    MIN,                        // Lexicographic minimum
    BYTE_MAX,                   // Bytewise maximum
    BYTE_MIN,                   // Bytewise minimum
    SET_VERSIONSTAMPED_KEY,     // Set with versionstamp in key
    SET_VERSIONSTAMPED_VALUE,   // Set with versionstamp in value
    APPEND_IF_FITS,             // Append if result fits
    COMPARE_AND_CLEAR,          // Clear if value matches
    // ... and 20+ other mutation types
}

Example: Common Mutations

// Counter increment
ByteBuffer buf = ByteBuffer.allocate(8);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.putLong(5L);
tr.mutate(MutationType.ADD, "counter".getBytes(), buf.array());

// Bitwise operations
tr.mutate(MutationType.BIT_OR, "flags".getBytes(), new byte[]{0x01});

// Max/Min
tr.mutate(MutationType.MAX, "max_value".getBytes(), "new_value".getBytes());

RangeQuery Class

Builder for range queries (returned by some range methods).

public RangeQuery limit(int limit)
public RangeQuery reverse(boolean reverse)
public RangeQuery streamingMode(StreamingMode mode)

RangeResult Class

Result container for range queries.

public Iterator<KeyValue> iterator()

KeyArrayResult Class

Array of keys returned from split points queries.

public int size()
public byte[] get(int index)

Error Handling

FDBException Class

Exception class for all FoundationDB errors.

public class FDBException extends RuntimeException {
    public int getCode()
}

Description: All FoundationDB errors throw FDBException with an error code.

Common Error Codes:

  • 1004: operation_cancelled - Transaction was cancelled
  • 1007: transaction_cancelled - Transaction was reset
  • 1009: request_maybe_delivered - Network error, retry required
  • 1020: not_committed - Transaction not committed, retry required
  • 1021: commit_unknown_result - Commit result unknown
  • 1031: transaction_too_old - Transaction read version too old
  • 1037: future_version - Read version is in the future
  • 2002: key_too_large - Key exceeds size limit

Example: Error Handling

try {
    db.run(tr -> {
        tr.set("key".getBytes(), "value".getBytes());
        return null;
    });
} catch (FDBException e) {
    System.err.println("Error code: " + e.getCode());
    System.err.println("Error message: " + e.getMessage());

    if (e.getCode() == 1031) {
        System.err.println("Transaction too old, operation took too long");
    }
}

Async Utilities

AsyncIterator<T> Interface

Iterator for asynchronous iteration.

public CompletableFuture<Boolean> onHasNext()
public T next()
public void cancel()

Description: Async iterator that fetches data on demand.

Example: Manual Async Iteration

AsyncIterable<KeyValue> range = tr.getRange("user:".getBytes(),
                                            "user;".getBytes());
AsyncIterator<KeyValue> iterator = range.iterator();

while (iterator.onHasNext().join()) {
    KeyValue kv = iterator.next();
    System.out.println(new String(kv.getKey()));
}

AsyncIterable<T> Interface

Represents an asynchronous sequence of items.

public AsyncIterator<T> iterator()
public CompletableFuture<List<T>> asList()

Description: Returned by range read operations. Can be iterated asynchronously or materialized to a list.

Example: Materialize to List

AsyncIterable<KeyValue> range = tr.getRange("user:".getBytes(),
                                            "user;".getBytes());

// Fetch all results as a list
List<KeyValue> allResults = range.asList().join();

System.out.println("Found " + allResults.size() + " results");

AsyncUtil Class

Utility class for async operations.

public static <T> CompletableFuture<List<T>> collect(AsyncIterable<T> iterable)
// ... and various other utility methods

Description: Provides utilities for working with async iterables and futures.

Example:

AsyncIterable<KeyValue> range = tr.getRange("data:".getBytes(),
                                            "data;".getBytes());

// Collect all results
List<KeyValue> results = AsyncUtil.collect(range).join();

Tuple Layer

The tuple layer provides type-safe encoding of structured keys.

Package: com.apple.foundationdb.tuple

Tuple Class

// Factory methods
public static Tuple from(Object... items)
public static Tuple fromBytes(byte[] bytes)
public static Tuple fromList(List<?> items)

// Encoding
public byte[] pack()
public byte[] packWithVersionstamp(byte[] prefix)

// Building
public Tuple add(Object item)
public Tuple addAll(Tuple t)

// Access
public List<Object> getItems()

// Range
public Range range()

Supported Types:

  • null - Null value
  • byte[] - Byte strings
  • String - Unicode strings
  • Long, Integer, Short, Byte - Integers
  • BigInteger - Large integers
  • Float, Double - Floating point
  • Boolean - Booleans
  • UUID - UUIDs
  • Versionstamp - Versionstamps
  • List<?> - Nested tuples

Example: Tuple Encoding

// Create tuple
Tuple tuple = Tuple.from("user", 1001, "name");

// Encode to bytes
byte[] key = tuple.pack();

// Use as database key
tr.set(key, "Alice".getBytes());

// Decode
Tuple decoded = Tuple.fromBytes(key);
System.out.println(decoded.getItems()); // ["user", 1001, "name"]

Example: User Records

// Store user data with structured keys
long userId = 1001;

Tuple nameKey = Tuple.from("user", userId, "name");
Tuple emailKey = Tuple.from("user", userId, "email");
Tuple ageKey = Tuple.from("user", userId, "age");

tr.set(nameKey.pack(), "Alice".getBytes());
tr.set(emailKey.pack(), "alice@example.com".getBytes());
tr.set(ageKey.pack(), "30".getBytes());

// Read all attributes for user
Range userRange = Tuple.from("user", userId).range();
AsyncIterable<KeyValue> attributes = tr.getRange(userRange);

for (KeyValue kv : attributes.asList().join()) {
    Tuple key = Tuple.fromBytes(kv.getKey());
    List<Object> parts = key.getItems();
    String attribute = (String) parts.get(2);
    String value = new String(kv.getValue());
    System.out.println(attribute + " = " + value);
}

Example: Nested Tuples

Tuple nested = Tuple.from(
    "event",
    1234567890L,
    Tuple.from("user", 1001), // Nested tuple
    "login"
);

byte[] encoded = nested.pack();

Versionstamp Class

Represents a versionstamp for monotonically increasing keys/values.

// Complete versionstamp
public Versionstamp(byte[] transactionVersion, int userVersion)

// Incomplete versionstamp (for use before commit)
public static Versionstamp incomplete(int userVersion)

Description: A versionstamp is a 10-byte value consisting of:

  • 8 bytes: Transaction commit version (filled in by database)
  • 2 bytes: User version (for ordering within transaction)

Example: Versionstamped Keys

// Create incomplete versionstamp
Versionstamp vs = Versionstamp.incomplete(0);

// Use in tuple
Tuple key = Tuple.from("event", vs, "data");

// Pack with versionstamp
byte[] packed = key.packWithVersionstamp(new byte[0]);

// Set using versionstamped key mutation
tr.mutate(MutationType.SET_VERSIONSTAMPED_KEY, packed, "event_data".getBytes());

// After commit, the versionstamp will be replaced with actual commit version

Example: Time-Ordered Events

db.run(tr -> {
    // Create event keys with versionstamps for automatic ordering
    for (int i = 0; i < 10; i++) {
        Versionstamp vs = Versionstamp.incomplete(i);
        Tuple key = Tuple.from("events", vs);
        byte[] packed = key.packWithVersionstamp(new byte[0]);

        tr.mutate(MutationType.SET_VERSIONSTAMPED_KEY,
                 packed,
                 ("Event " + i).getBytes());
    }
    return null;
});

// Events are now stored in commit version order

Subspace

Subspaces provide key prefixing and namespace isolation.

Package: com.apple.foundationdb.subspace

Subspace Class

// Constructors
public Subspace()
public Subspace(byte[] rawPrefix)
public Subspace(Tuple prefix)

// Key operations
public byte[] getKey()
public byte[] pack(Tuple tuple)
public byte[] pack()
public Tuple unpack(byte[] key)

// Range operations
public Range range(Tuple tuple)
public Range range()

// Containment
public boolean contains(byte[] key)

// Nesting
public Subspace get(Tuple tuple)

Example: Basic Subspace

// Create subspace for users
Subspace users = new Subspace(Tuple.from("users"));

// Pack keys within subspace
byte[] userKey = users.pack(Tuple.from(1001, "name"));
tr.set(userKey, "Alice".getBytes());

// Get range for all users
Range allUsers = users.range();
AsyncIterable<KeyValue> results = tr.getRange(allUsers);

Example: Nested Subspaces

// Application subspace
Subspace app = new Subspace(Tuple.from("myapp"));

// Nested subspaces
Subspace users = app.get(Tuple.from("users"));
Subspace posts = app.get(Tuple.from("posts"));
Subspace comments = app.get(Tuple.from("comments"));

// Store user data
byte[] userKey = users.pack(Tuple.from(1001, "name"));
tr.set(userKey, "Alice".getBytes());

// Store post data
byte[] postKey = posts.pack(Tuple.from(5001, "title"));
tr.set(postKey, "Hello World".getBytes());

// Each subspace is isolated

Example: Check Containment

Subspace users = new Subspace(Tuple.from("users"));

byte[] userKey = users.pack(Tuple.from(1001));
byte[] otherKey = "other:key".getBytes();

System.out.println(users.contains(userKey));  // true
System.out.println(users.contains(otherKey)); // false

Directory Layer

The directory layer provides dynamic hierarchical prefix allocation.

Package: com.apple.foundationdb.directory

DirectoryLayer Class

Manages the directory system.

// Constructors
public DirectoryLayer()
public DirectoryLayer(Subspace nodeSubspace, Subspace contentSubspace)
public DirectoryLayer(Subspace nodeSubspace, Subspace contentSubspace,
                     boolean allowManualPrefixes)

Directory Interface

Interface for directory operations.

// Create or open
public CompletableFuture<DirectorySubspace> createOrOpen(
    TransactionContext tc, List<String> path, byte[] layer)
public CompletableFuture<DirectorySubspace> createOrOpen(
    TransactionContext tc, List<String> path)

// Create
public CompletableFuture<DirectorySubspace> create(
    TransactionContext tc, List<String> path, byte[] layer, byte[] prefix)
public CompletableFuture<DirectorySubspace> create(
    TransactionContext tc, List<String> path, byte[] layer)
public CompletableFuture<DirectorySubspace> create(
    TransactionContext tc, List<String> path)

// Open
public CompletableFuture<DirectorySubspace> open(
    ReadTransactionContext tc, List<String> path, byte[] layer)
public CompletableFuture<DirectorySubspace> open(
    ReadTransactionContext tc, List<String> path)

// Exists
public CompletableFuture<Boolean> exists(
    ReadTransactionContext tc, List<String> path)

// List
public CompletableFuture<List<String>> list(
    ReadTransactionContext tc, List<String> path)
public CompletableFuture<List<String>> list(ReadTransactionContext tc)

// Move
public CompletableFuture<DirectorySubspace> move(
    TransactionContext tc, List<String> oldPath, List<String> newPath)

// Remove
public CompletableFuture<Boolean> remove(TransactionContext tc, List<String> path)
public CompletableFuture<Boolean> removeIfExists(TransactionContext tc,
                                                  List<String> path)

Description: Directories provide:

  • Automatic prefix allocation: Each directory gets a unique short prefix
  • Path-based organization: Hierarchical paths like file systems
  • Layer annotation: Tag directories with layer identifiers
  • Transactional operations: All operations are transactional

DirectorySubspace Class

Combines Directory and Subspace functionality.

// From Subspace
public byte[] getKey()
public byte[] pack(Tuple tuple)
public Tuple unpack(byte[] key)
public Range range(Tuple tuple)
public boolean contains(byte[] key)

// Directory-specific
public byte[] getLayer()
public List<String> getPath()

Example: Basic Directory Usage

DirectoryLayer rootDir = new DirectoryLayer();

// Create directory
DirectorySubspace users = rootDir.createOrOpen(db,
    Arrays.asList("myapp", "users")).join();

// The directory automatically gets a short prefix
byte[] prefix = users.getKey();
System.out.println("Users prefix: " + bytesToHex(prefix));

// Use like a subspace
byte[] key = users.pack(Tuple.from(1001, "name"));
db.run(tr -> {
    tr.set(key, "Alice".getBytes());
    return null;
});

Example: Directory Hierarchy

DirectoryLayer rootDir = new DirectoryLayer();

// Create hierarchical structure
DirectorySubspace app = rootDir.createOrOpen(db,
    Arrays.asList("myapp")).join();

DirectorySubspace users = rootDir.createOrOpen(db,
    Arrays.asList("myapp", "users")).join();

DirectorySubspace posts = rootDir.createOrOpen(db,
    Arrays.asList("myapp", "posts")).join();

DirectorySubspace comments = rootDir.createOrOpen(db,
    Arrays.asList("myapp", "posts", "comments")).join();

// Each gets a unique short prefix
System.out.println("App: " + bytesToHex(app.getKey()));
System.out.println("Users: " + bytesToHex(users.getKey()));
System.out.println("Posts: " + bytesToHex(posts.getKey()));
System.out.println("Comments: " + bytesToHex(comments.getKey()));

Example: List Directories

DirectoryLayer rootDir = new DirectoryLayer();

// Create some directories
rootDir.createOrOpen(db, Arrays.asList("app1", "users")).join();
rootDir.createOrOpen(db, Arrays.asList("app1", "posts")).join();
rootDir.createOrOpen(db, Arrays.asList("app1", "comments")).join();

// List subdirectories
List<String> subdirs = rootDir.list(db, Arrays.asList("app1")).join();
System.out.println("Subdirectories: " + subdirs);
// Output: [users, posts, comments]

Example: Move Directory

DirectoryLayer rootDir = new DirectoryLayer();

// Create directory
rootDir.createOrOpen(db, Arrays.asList("temp", "data")).join();

// Move it
DirectorySubspace moved = rootDir.move(db,
    Arrays.asList("temp", "data"),
    Arrays.asList("permanent", "data")
).join();

System.out.println("New path: " + moved.getPath());

Example: Remove Directory

DirectoryLayer rootDir = new DirectoryLayer();

// Remove directory and all its contents
boolean removed = rootDir.remove(db, Arrays.asList("temp", "data")).join();

if (removed) {
    System.out.println("Directory removed");
}

Example: Layer Annotation

DirectoryLayer rootDir = new DirectoryLayer();

// Create directory with layer annotation
DirectorySubspace counters = rootDir.create(db,
    Arrays.asList("app", "counters"),
    "counter_layer".getBytes()
).join();

// Open with layer verification
try {
    DirectorySubspace opened = rootDir.open(db,
        Arrays.asList("app", "counters"),
        "counter_layer".getBytes()
    ).join();
    System.out.println("Layer matches!");
} catch (Exception e) {
    System.err.println("Layer mismatch!");
}

Options Interfaces

NetworkOptions Interface

Configure network-level settings.

public NetworkOptions options()

Available options include:

  • TLS configuration
  • Trace logging
  • Client buggification
  • External client library

Example:

FDB fdb = FDB.selectAPIVersion(740);
fdb.options().setTraceEnable("/var/log/fdb");

DatabaseOptions Interface

Configure database-level settings.

public DatabaseOptions options()

Available options include:

  • Location cache size
  • Machine ID
  • Transaction timeout
  • Transaction retry limit
  • Snapshot read-your-writes

Example:

Database db = fdb.open();
db.options().setLocationCacheSize(100000);
db.options().setTransactionTimeout(5000); // 5 seconds

TenantOptions Interface

Configure tenant-level settings.

public TenantOptions options()

TransactionOptions Interface

Configure transaction-level settings.

public TransactionOptions options()

Available options include:

  • Timeout
  • Retry limit
  • Causal read risky
  • Priority (batch/default/immediate)
  • Read your writes disable
  • Access system keys
  • Read system keys

Example:

db.run(tr -> {
    // Set 1 second timeout
    tr.options().setTimeout(1000);

    // Set max 3 retries
    tr.options().setRetryLimit(3);

    // Set batch priority
    tr.options().setPriorityBatch();

    // Perform operations...
    return null;
});

Complete Examples

Example 1: User Management System

import com.apple.foundationdb.*;
import com.apple.foundationdb.tuple.*;
import com.apple.foundationdb.subspace.*;
import java.util.*;

public class UserManagement {
    private Database db;
    private Subspace users;

    public UserManagement() {
        FDB fdb = FDB.selectAPIVersion(740);
        this.db = fdb.open();
        this.users = new Subspace(Tuple.from("users"));
    }

    public void createUser(long userId, String name, String email) {
        db.run(tr -> {
            byte[] nameKey = users.pack(Tuple.from(userId, "name"));
            byte[] emailKey = users.pack(Tuple.from(userId, "email"));

            tr.set(nameKey, name.getBytes());
            tr.set(emailKey, email.getBytes());

            return null;
        });
    }

    public Map<String, String> getUser(long userId) {
        return db.read(tr -> {
            byte[] nameKey = users.pack(Tuple.from(userId, "name"));
            byte[] emailKey = users.pack(Tuple.from(userId, "email"));

            byte[] name = tr.get(nameKey).join();
            byte[] email = tr.get(emailKey).join();

            if (name == null) return null;

            Map<String, String> user = new HashMap<>();
            user.put("name", new String(name));
            user.put("email", email != null ? new String(email) : null);

            return user;
        });
    }

    public void deleteUser(long userId) {
        db.run(tr -> {
            Range userRange = users.range(Tuple.from(userId));
            tr.clear(userRange);
            return null;
        });
    }

    public List<Long> listUserIds() {
        return db.read(tr -> {
            Range allUsers = users.range();
            AsyncIterable<KeyValue> results = tr.getRange(allUsers);

            Set<Long> userIds = new HashSet<>();
            for (KeyValue kv : results.asList().join()) {
                Tuple key = users.unpack(kv.getKey());
                long userId = (long) key.getItems().get(0);
                userIds.add(userId);
            }

            return new ArrayList<>(userIds);
        });
    }
}

Example 2: Atomic Counter

import com.apple.foundationdb.*;
import java.nio.*;

public class AtomicCounter {
    private Database db;
    private byte[] counterKey;

    public AtomicCounter(String counterName) {
        FDB fdb = FDB.selectAPIVersion(740);
        this.db = fdb.open();
        this.counterKey = ("counter:" + counterName).getBytes();
    }

    public void increment(long delta) {
        db.run(tr -> {
            ByteBuffer buf = ByteBuffer.allocate(8);
            buf.order(ByteOrder.LITTLE_ENDIAN);
            buf.putLong(delta);

            tr.mutate(MutationType.ADD, counterKey, buf.array());
            return null;
        });
    }

    public long getValue() {
        return db.read(tr -> {
            byte[] value = tr.get(counterKey).join();
            if (value == null) return 0L;

            ByteBuffer buf = ByteBuffer.wrap(value);
            buf.order(ByteOrder.LITTLE_ENDIAN);
            return buf.getLong();
        });
    }

    public void reset() {
        db.run(tr -> {
            tr.clear(counterKey);
            return null;
        });
    }
}

Example 3: Queue Implementation

import com.apple.foundationdb.*;
import com.apple.foundationdb.tuple.*;
import com.apple.foundationdb.subspace.*;
import java.util.*;

public class FDBQueue {
    private Database db;
    private Subspace queueSubspace;

    public FDBQueue(String queueName) {
        FDB fdb = FDB.selectAPIVersion(740);
        this.db = fdb.open();
        this.queueSubspace = new Subspace(Tuple.from("queue", queueName));
    }

    public void enqueue(byte[] item) {
        db.run(tr -> {
            // Use versionstamp for automatic ordering
            Versionstamp vs = Versionstamp.incomplete(0);
            Tuple key = Tuple.from(vs);
            byte[] packed = queueSubspace.pack(key).packWithVersionstamp(
                queueSubspace.pack());

            tr.mutate(MutationType.SET_VERSIONSTAMPED_KEY, packed, item);
            return null;
        });
    }

    public byte[] dequeue() {
        return db.run(tr -> {
            // Get first item
            Range range = queueSubspace.range();
            AsyncIterable<KeyValue> results = tr.getRange(range, 1);

            List<KeyValue> items = results.asList().join();
            if (items.isEmpty()) return null;

            KeyValue first = items.get(0);
            byte[] value = first.getValue();

            // Remove from queue
            tr.clear(first.getKey());

            return value;
        });
    }

    public int size() {
        return db.read(tr -> {
            Range range = queueSubspace.range();
            AsyncIterable<KeyValue> results = tr.getRange(range);
            return results.asList().join().size();
        });
    }
}

Example 4: Range Partitioning

import com.apple.foundationdb.*;
import com.apple.foundationdb.tuple.*;
import java.util.*;
import java.util.concurrent.*;

public class ParallelRangeProcessor {
    private Database db;

    public ParallelRangeProcessor() {
        FDB fdb = FDB.selectAPIVersion(740);
        this.db = fdb.open();
    }

    public void processRangeInParallel(byte[] begin, byte[] end,
                                       long chunkSize) {
        // Get split points
        KeyArrayResult splitPoints = db.read(tr -> {
            return tr.getRangeSplitPoints(begin, end, chunkSize).join();
        });

        // Create chunks
        List<Range> chunks = new ArrayList<>();
        byte[] chunkBegin = begin;

        for (int i = 0; i < splitPoints.size(); i++) {
            byte[] chunkEnd = splitPoints.get(i);
            chunks.add(new Range(chunkBegin, chunkEnd));
            chunkBegin = chunkEnd;
        }
        chunks.add(new Range(chunkBegin, end));

        // Process chunks in parallel
        ExecutorService executor = Executors.newFixedThreadPool(
            Runtime.getRuntime().availableProcessors());

        List<CompletableFuture<Void>> futures = new ArrayList<>();

        for (Range chunk : chunks) {
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                processChunk(chunk);
            }, executor);
            futures.add(future);
        }

        // Wait for all chunks
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        executor.shutdown();
    }

    private void processChunk(Range range) {
        db.read(tr -> {
            AsyncIterable<KeyValue> results = tr.getRange(range);

            for (KeyValue kv : results.asList().join()) {
                // Process each key-value pair
                processKeyValue(kv);
            }

            return null;
        });
    }

    private void processKeyValue(KeyValue kv) {
        // Custom processing logic
        System.out.println("Processing: " + new String(kv.getKey()));
    }
}

Best Practices

Transaction Patterns

Use Automatic Retry:

// Good - automatic retry
db.run(tr -> {
    tr.set(key, value);
    return null;
});

// Avoid - manual retry is error-prone
Transaction tr = db.createTransaction();
while (true) {
    try {
        tr.set(key, value);
        tr.commit().join();
        break;
    } catch (Exception e) {
        tr.onError(e).join();
    }
}

Keep Transactions Short:

// Good - minimal transaction scope
long balance = db.read(tr -> {
    byte[] value = tr.get(key).join();
    return parseBalance(value);
});

// Process data outside transaction
processBalance(balance);

// Good - separate write transaction
db.run(tr -> {
    tr.set(key, newValue);
    return null;
});

// Avoid - long-running transaction
db.run(tr -> {
    byte[] value = tr.get(key).join();
    long balance = parseBalance(value);

    // BAD: Long processing inside transaction
    Thread.sleep(10000);
    processBalance(balance);

    tr.set(key, newValue);
    return null;
});

Key Design

Use Tuple Layer:

// Good - structured keys
Tuple key = Tuple.from("user", userId, "email");
tr.set(key.pack(), email.getBytes());

// Avoid - string concatenation
String key = "user:" + userId + ":email";
tr.set(key.getBytes(), email.getBytes());

Use Subspaces for Isolation:

Subspace users = new Subspace(Tuple.from("users"));
Subspace posts = new Subspace(Tuple.from("posts"));

// Keys are automatically isolated
byte[] userKey = users.pack(Tuple.from(userId));
byte[] postKey = posts.pack(Tuple.from(postId));

Error Handling

Handle Non-Retryable Errors:

try {
    db.run(tr -> {
        // Operations...
        return null;
    });
} catch (FDBException e) {
    if (e.getCode() == 2002) {
        // Key too large - this won't succeed on retry
        handleKeyTooLarge();
    } else {
        throw e; // Let automatic retry handle it
    }
}

Resource Management

Use Try-With-Resources:

// Good - automatic cleanup
try (Database db = fdb.open()) {
    // Use database
}

// Good for manual transactions
try (Transaction tr = db.createTransaction()) {
    // Use transaction
}

Performance

Use Snapshot Reads for Non-Serializable Queries:

db.read(tr -> {
    // Snapshot read - no conflicts
    byte[] stats = tr.snapshot().get("statistics".getBytes()).join();
    return stats;
});

Batch Operations:

// Good - batch multiple sets
db.run(tr -> {
    for (Map.Entry<String, String> entry : data.entrySet()) {
        tr.set(entry.getKey().getBytes(), entry.getValue().getBytes());
    }
    return null;
});

// Avoid - separate transactions for each set
for (Map.Entry<String, String> entry : data.entrySet()) {
    db.run(tr -> {
        tr.set(entry.getKey().getBytes(), entry.getValue().getBytes());
        return null;
    });
}

Additional Resources

This reference covers FoundationDB Java API version 7.4.5. For the latest updates, consult the official documentation.