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
import com.apple.foundationdb.*;Core classes for database connectivity, transactions, and data operations.
import com.apple.foundationdb.async.*;Utilities for asynchronous iteration and future composition.
import com.apple.foundationdb.tuple.*;Type-safe encoding/decoding of structured keys using tuples.
import com.apple.foundationdb.subspace.*;Key prefixing and namespace isolation.
import com.apple.foundationdb.directory.*;Dynamic hierarchical prefix allocation and management.
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 controlExample: 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));
}
}
}
}The Database interface represents a connection to FoundationDB and provides 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();
}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 resulte: 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;
});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;
});public DatabaseOptions options()Description: Get database options interface for configuration.
The Tenant interface provides the same transactional capabilities as Database, but scoped to a tenant namespace.
public Transaction createTransaction()
public Transaction createTransaction(Executor e)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 namespaceThe TenantManagement class provides static utility methods for managing tenants in a FoundationDB database.
Package: com.apple.foundationdb.TenantManagement
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 contexttenantName: Tenant name as bytes or TupleReturns: 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;
});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 contexttenantName: Tenant name as bytes or TupleReturns: 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();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 contextbegin: Start of tenant name range (inclusive)end: End of tenant name range (exclusive)limit: Maximum number of tenants to returnReturns: 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...
}The LocalityUtil class provides static utility methods for querying data locality and distribution information.
Package: com.apple.foundationdb.LocalityUtil
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 contextbegin: 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...
}
}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 queryReturns: 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:
The ReadTransaction interface provides read-only operations. This is the base interface for Transaction.
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");
}public CompletableFuture<byte[]> getKey(KeySelector selector)Description: Resolve a key selector to an actual key asynchronously.
Parameters:
selector: KeySelector specifying the key to findReturns: 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 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 ordermode: Streaming mode controlling fetch behaviorReturns: 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);
}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.
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();
}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");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 boundarieschunkSize: Desired chunk size in bytesReturns: 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);
}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();public TransactionOptions options()Description: Get transaction options interface for configuration.
public CompletableFuture<KeyRangeArrayResult> getBlobGranuleRanges(byte[] begin, byte[] end,
int rowLimit)Description: Get blob granule ranges for time-travel queries (requires blob granule configuration).
The Transaction interface extends ReadTransaction with write operations and transaction control.
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()));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 mutateparam: Parameter for the operation (interpretation depends on opType)Common MutationType Values:
ADD: Add integer value (little-endian encoded)BIT_AND: Bitwise ANDBIT_OR: Bitwise ORBIT_XOR: Bitwise XORMAX: 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 paramAPPEND_IF_FITS: Append if result fits in value size limitSET_VERSIONSTAMPED_KEY: Set with versionstamp in keySET_VERSIONSTAMPED_VALUE: Set with versionstamp in valueExample: 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 versionpublic 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 handleReturns: 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.
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 watchReturns: 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();
});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");
}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());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);
}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 keyorEqual: Whether to include the reference keyoffset: 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();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);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 bandwidthWANT_ALL: Fetch all data at once (good for small ranges)EXACT: Fetch exactly the specified limitSMALL/MEDIUM/LARGE: Hints about expected data sizeSERIAL: Minimize latency at the cost of bandwidthExample:
// 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
);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());Builder for range queries (returned by some range methods).
public RangeQuery limit(int limit)
public RangeQuery reverse(boolean reverse)
public RangeQuery streamingMode(StreamingMode mode)Result container for range queries.
public Iterator<KeyValue> iterator()Array of keys returned from split points queries.
public int size()
public byte[] get(int index)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 cancelled1007: transaction_cancelled - Transaction was reset1009: request_maybe_delivered - Network error, retry required1020: not_committed - Transaction not committed, retry required1021: commit_unknown_result - Commit result unknown1031: transaction_too_old - Transaction read version too old1037: future_version - Read version is in the future2002: key_too_large - Key exceeds size limitExample: 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");
}
}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()));
}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");Utility class for async operations.
public static <T> CompletableFuture<List<T>> collect(AsyncIterable<T> iterable)
// ... and various other utility methodsDescription: 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();The tuple layer provides type-safe encoding of structured keys.
Package: com.apple.foundationdb.tuple
// 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 valuebyte[] - Byte stringsString - Unicode stringsLong, Integer, Short, Byte - IntegersBigInteger - Large integersFloat, Double - Floating pointBoolean - BooleansUUID - UUIDsVersionstamp - VersionstampsList<?> - Nested tuplesExample: 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();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:
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 versionExample: 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 orderSubspaces provide key prefixing and namespace isolation.
Package: com.apple.foundationdb.subspace
// 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 isolatedExample: 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)); // falseThe directory layer provides dynamic hierarchical prefix allocation.
Package: com.apple.foundationdb.directory
Manages the directory system.
// Constructors
public DirectoryLayer()
public DirectoryLayer(Subspace nodeSubspace, Subspace contentSubspace)
public DirectoryLayer(Subspace nodeSubspace, Subspace contentSubspace,
boolean allowManualPrefixes)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:
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!");
}Configure network-level settings.
public NetworkOptions options()Available options include:
Example:
FDB fdb = FDB.selectAPIVersion(740);
fdb.options().setTraceEnable("/var/log/fdb");Configure database-level settings.
public DatabaseOptions options()Available options include:
Example:
Database db = fdb.open();
db.options().setLocationCacheSize(100000);
db.options().setTransactionTimeout(5000); // 5 secondsConfigure tenant-level settings.
public TenantOptions options()Configure transaction-level settings.
public TransactionOptions options()Available options include:
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;
});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);
});
}
}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;
});
}
}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();
});
}
}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()));
}
}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;
});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));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
}
}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
}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;
});
}This reference covers FoundationDB Java API version 7.4.5. For the latest updates, consult the official documentation.