or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

blob-granules.mdc-api.mdgo-api.mdindex.mdjava-api.mdkey-encoding.mdmulti-tenancy.mdpython-api.mdruby-api.md

java-api.mddocs/

0

# Java API Reference

1

2

The FoundationDB Java API provides an object-oriented interface to the database with CompletableFuture-based asynchronous operations, functional transaction interfaces, and comprehensive type safety. The API follows Java conventions and integrates well with modern Java applications.

3

4

## Installation

5

6

### Maven

7

8

```xml

9

<dependency>

10

<groupId>org.foundationdb</groupId>

11

<artifactId>fdb-java</artifactId>

12

<version>7.4.3</version>

13

</dependency>

14

```

15

16

### Gradle

17

18

```gradle

19

implementation 'org.foundationdb:fdb-java:7.4.3'

20

```

21

22

## Core Imports

23

24

```java

25

import com.apple.foundationdb.*;

26

import com.apple.foundationdb.async.AsyncIterable;

27

import com.apple.foundationdb.tuple.Tuple;

28

import com.apple.foundationdb.subspace.Subspace;

29

import com.apple.foundationdb.directory.Directory;

30

import java.util.concurrent.CompletableFuture;

31

```

32

33

## Capabilities

34

35

### Initialization and Database Connection

36

37

API version selection and database connection management.

38

39

```java { .api }

40

public class FDB {

41

/**

42

* Select API version and get FDB instance (must be called first).

43

*

44

* @param version API version to use (typically 740 for v7.4.0+)

45

* @return FDB instance for opening databases

46

*/

47

public static FDB selectAPIVersion(int version);

48

49

/**

50

* Get the current FDB instance.

51

*

52

* @return Current FDB instance

53

*/

54

public static FDB instance();

55

56

/**

57

* Open database with default cluster file.

58

*

59

* @return Database handle for creating transactions

60

*/

61

public Database open();

62

63

/**

64

* Open database with specified cluster file.

65

*

66

* @param clusterFilePath Path to cluster file

67

* @return Database handle for creating transactions

68

*/

69

public Database open(String clusterFilePath);

70

71

/**

72

* Start network thread (called automatically by open()).

73

*/

74

public void startNetwork();

75

76

/**

77

* Stop network thread.

78

*/

79

public void stopNetwork();

80

81

/**

82

* Set network-level options.

83

*

84

* @param option Network option to set

85

* @param value Option value

86

*/

87

public void setNetworkOption(NetworkOptions option, byte[] value);

88

}

89

```

90

91

**Usage Example:**

92

93

```java

94

import com.apple.foundationdb.*;

95

96

// Initialize FoundationDB (required first step)

97

FDB fdb = FDB.selectAPIVersion(740);

98

99

// Open database connection

100

Database db = fdb.open(); // Uses default cluster file

101

// or specify cluster file path

102

Database db = fdb.open("/etc/foundationdb/fdb.cluster");

103

```

104

105

### Database Operations

106

107

Database interface for creating transactions and managing tenants.

108

109

```java { .api }

110

public interface Database extends Transactor, ReadTransactor {

111

/**

112

* Create a new transaction.

113

*

114

* @return Transaction handle for database operations

115

*/

116

Transaction createTransaction();

117

118

/**

119

* Execute function with automatic transaction retry.

120

*

121

* @param <T> Return type

122

* @param retryable Function to execute with retry logic

123

* @return Result of the function

124

*/

125

<T> T run(Function<Transaction, T> retryable);

126

127

/**

128

* Execute read-only function with automatic retry.

129

*

130

* @param <T> Return type

131

* @param retryable Read-only function to execute

132

* @return Result of the function

133

*/

134

<T> T read(Function<ReadTransaction, T> retryable);

135

136

/**

137

* Open tenant handle for multi-tenancy.

138

*

139

* @param tenantName Name of the tenant to open

140

* @return Tenant handle with isolated keyspace

141

*/

142

Tenant openTenant(byte[] tenantName);

143

144

/**

145

* Set database-level options.

146

*

147

* @param option Database option to set

148

* @param value Option value

149

*/

150

void setOption(DatabaseOptions option, byte[] value);

151

}

152

```

153

154

### Transactor Interfaces

155

156

Functional interfaces for executing transactional operations.

157

158

```java { .api }

159

public interface Transactor {

160

/**

161

* Execute function with automatic transaction retry.

162

*

163

* @param <T> Return type

164

* @param retryable Function to execute with retry logic

165

* @return Result of the function

166

*/

167

<T> T run(Function<Transaction, T> retryable);

168

}

169

170

public interface ReadTransactor {

171

/**

172

* Execute read-only function with automatic retry.

173

*

174

* @param <T> Return type

175

* @param retryable Read-only function to execute

176

* @return Result of the function

177

*/

178

<T> T read(Function<ReadTransaction, T> retryable);

179

}

180

```

181

182

**Usage Example:**

183

184

```java

185

Database db = fdb.open();

186

187

// Functional transaction handling

188

String result = db.run(tr -> {

189

byte[] value = tr.get("my_key".getBytes()).join();

190

if (value == null) {

191

tr.set("my_key".getBytes(), "initial_value".getBytes());

192

return "created";

193

} else {

194

return new String(value);

195

}

196

});

197

198

// Read-only transaction

199

String readResult = db.read(tr -> {

200

byte[] value = tr.get("my_key".getBytes()).join();

201

return value != null ? new String(value) : "not_found";

202

});

203

```

204

205

### Transaction Operations

206

207

Core transaction operations for reading and writing data.

208

209

```java { .api }

210

public interface Transaction extends ReadTransaction {

211

/**

212

* Write a key-value pair.

213

*

214

* @param key Key to write

215

* @param value Value to write

216

*/

217

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

218

219

/**

220

* Delete a single key.

221

*

222

* @param key Key to delete

223

*/

224

void clear(byte[] key);

225

226

/**

227

* Delete a range of keys.

228

*

229

* @param begin Start key (inclusive)

230

* @param end End key (exclusive)

231

*/

232

void clearRange(byte[] begin, byte[] end);

233

234

/**

235

* Commit the transaction.

236

*

237

* @return CompletableFuture that completes when commit finishes

238

*/

239

CompletableFuture<Void> commit();

240

241

/**

242

* Handle transaction errors and determine retry logic.

243

*

244

* @param error Exception that occurred

245

* @return CompletableFuture that completes when ready to retry

246

*/

247

CompletableFuture<Void> onError(Throwable error);

248

249

/**

250

* Get the read version for this transaction.

251

*

252

* @return CompletableFuture containing the read version

253

*/

254

CompletableFuture<Long> getReadVersion();

255

256

/**

257

* Set the read version for this transaction.

258

*

259

* @param version Read version to use

260

*/

261

void setReadVersion(long version);

262

263

/**

264

* Get the version at which this transaction was committed.

265

* Only valid after successful commit.

266

*

267

* @return Committed version number

268

*/

269

long getCommittedVersion();

270

271

/**

272

* Get the versionstamp for this transaction.

273

* Only valid after successful commit.

274

*

275

* @return CompletableFuture containing the 12-byte versionstamp

276

*/

277

CompletableFuture<byte[]> getVersionstamp();

278

279

/**

280

* Add a range to the transaction's read conflict ranges.

281

*

282

* @param begin Start key (inclusive)

283

* @param end End key (exclusive)

284

*/

285

void addReadConflictRange(byte[] begin, byte[] end);

286

287

/**

288

* Add a range to the transaction's write conflict ranges.

289

*

290

* @param begin Start key (inclusive)

291

* @param end End key (exclusive)

292

*/

293

void addWriteConflictRange(byte[] begin, byte[] end);

294

295

/**

296

* Set transaction-level options.

297

*

298

* @param option Transaction option to set

299

* @param value Option value

300

*/

301

void setOption(TransactionOptions option, byte[] value);

302

}

303

304

public interface ReadTransaction {

305

/**

306

* Read a single key.

307

*

308

* @param key Key to read

309

* @return CompletableFuture containing the value (null if key doesn't exist)

310

*/

311

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

312

313

/**

314

* Read a range of key-value pairs.

315

*

316

* @param begin Start key selector

317

* @param end End key selector

318

* @return AsyncIterable of KeyValue objects

319

*/

320

AsyncIterable<KeyValue> getRange(KeySelector begin, KeySelector end);

321

322

/**

323

* Read a range of key-value pairs with options.

324

*

325

* @param begin Start key selector

326

* @param end End key selector

327

* @param limit Maximum number of pairs to return (0 for no limit)

328

* @param reverse Whether to read in reverse order

329

* @param mode Streaming mode for result batching

330

* @return AsyncIterable of KeyValue objects

331

*/

332

AsyncIterable<KeyValue> getRange(KeySelector begin, KeySelector end,

333

int limit, boolean reverse, StreamingMode mode);

334

335

/**

336

* Read a range using raw key boundaries.

337

*

338

* @param begin Start key (inclusive)

339

* @param end End key (exclusive)

340

* @return AsyncIterable of KeyValue objects

341

*/

342

AsyncIterable<KeyValue> getRange(byte[] begin, byte[] end);

343

344

/**

345

* Read a range using Range object.

346

*

347

* @param range Range specification

348

* @return AsyncIterable of KeyValue objects

349

*/

350

AsyncIterable<KeyValue> getRange(Range range);

351

352

/**

353

* Get estimated byte size of a range.

354

*

355

* @param begin Start key

356

* @param end End key

357

* @return CompletableFuture containing estimated size in bytes

358

*/

359

CompletableFuture<Long> getEstimatedRangeSize(byte[] begin, byte[] end);

360

361

/**

362

* Get boundary keys for a range (used for parallel processing).

363

*

364

* @param begin Start key

365

* @param end End key

366

* @return AsyncIterable of boundary keys

367

*/

368

AsyncIterable<byte[]> getRangeSplitPoints(byte[] begin, byte[] end, long chunkSize);

369

370

/**

371

* Get the read version that this transaction will use.

372

*

373

* @return CompletableFuture containing the read version

374

*/

375

CompletableFuture<Long> getReadVersion();

376

377

/**

378

* Perform a snapshot read (no conflicts).

379

*

380

* @return ReadTransaction for snapshot operations

381

*/

382

ReadTransaction snapshot();

383

}

384

```

385

386

### Key Selectors and Range Operations

387

388

Advanced key selection and range query operations.

389

390

```java { .api }

391

public class KeySelector {

392

/**

393

* Create a key selector.

394

*

395

* @param key Reference key

396

* @param orEqual Whether to include the reference key

397

* @param offset Offset from the reference key

398

*/

399

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

400

401

/**

402

* Key selector for last key less than given key.

403

*/

404

public static KeySelector lastLessThan(byte[] key);

405

406

/**

407

* Key selector for last key less than or equal to given key.

408

*/

409

public static KeySelector lastLessOrEqual(byte[] key);

410

411

/**

412

* Key selector for first key greater than given key.

413

*/

414

public static KeySelector firstGreaterThan(byte[] key);

415

416

/**

417

* Key selector for first key greater than or equal to given key.

418

*/

419

public static KeySelector firstGreaterOrEqual(byte[] key);

420

421

public byte[] getKey();

422

public boolean getOrEqual();

423

public int getOffset();

424

}

425

426

public class KeyValue {

427

/**

428

* Key-value pair from range operations.

429

*/

430

public byte[] getKey();

431

public byte[] getValue();

432

}

433

434

public class Range {

435

/**

436

* Key range specification.

437

*/

438

public Range(byte[] begin, byte[] end);

439

440

public byte[] begin;

441

public byte[] end;

442

443

public static Range startsWith(byte[] prefix);

444

}

445

```

446

447

### Tenant Operations

448

449

Multi-tenancy support for isolated keyspaces.

450

451

```java { .api }

452

public interface Tenant extends Transactor, ReadTransactor {

453

/**

454

* Create a new transaction within this tenant's keyspace.

455

*

456

* @return Transaction handle scoped to this tenant

457

*/

458

Transaction createTransaction();

459

460

/**

461

* Get the tenant name.

462

*

463

* @return Tenant name as byte array

464

*/

465

byte[] getName();

466

}

467

```

468

469

### Exception Handling

470

471

Exception classes for error handling.

472

473

```java { .api }

474

public class FDBException extends RuntimeException {

475

/**

476

* Base exception class for FoundationDB errors.

477

*

478

* @param message Error message

479

* @param code FoundationDB error code

480

*/

481

public FDBException(String message, int code);

482

483

/**

484

* Get the FoundationDB error code.

485

*

486

* @return Error code

487

*/

488

public int getCode();

489

490

/**

491

* Check if error indicates transaction should be retried.

492

*

493

* @return true if error is retryable

494

*/

495

public boolean isRetryable();

496

497

/**

498

* Check if transaction may have been committed despite error.

499

*

500

* @return true if transaction may have committed

501

*/

502

public boolean isMaybeCommitted();

503

504

/**

505

* Check if error is retryable and transaction was not committed.

506

*

507

* @return true if safe to retry (not committed)

508

*/

509

public boolean isRetryableNotCommitted();

510

}

511

```

512

513

### Tuple Encoding

514

515

Structured key encoding for hierarchical data organization.

516

517

```java { .api }

518

public class Tuple {

519

/**

520

* Create empty tuple.

521

*/

522

public Tuple();

523

524

/**

525

* Create tuple from items.

526

*

527

* @param items Values to include in tuple

528

*/

529

public Tuple(Object... items);

530

531

/**

532

* Add item to tuple.

533

*

534

* @param item Item to add

535

* @return This tuple for method chaining

536

*/

537

public Tuple add(Object item);

538

539

/**

540

* Add all items to tuple.

541

*

542

* @param items Items to add

543

* @return This tuple for method chaining

544

*/

545

public Tuple addAll(Object... items);

546

547

/**

548

* Get item at index.

549

*

550

* @param index Index of item to get

551

* @return Item at index

552

*/

553

public Object get(int index);

554

555

/**

556

* Get number of items in tuple.

557

*

558

* @return Number of items

559

*/

560

public int size();

561

562

/**

563

* Convert tuple to list.

564

*

565

* @return List containing tuple items

566

*/

567

public List<Object> getItems();

568

569

/**

570

* Encode tuple to bytes for use as key.

571

*

572

* @return Encoded bytes suitable for use as FoundationDB key

573

*/

574

public byte[] pack();

575

576

/**

577

* Decode bytes back to tuple.

578

*

579

* @param data Encoded tuple bytes

580

* @return Decoded tuple

581

*/

582

public static Tuple fromBytes(byte[] data);

583

584

/**

585

* Create key range for all tuples with this tuple as prefix.

586

*

587

* @return Range for prefix queries

588

*/

589

public Range range();

590

591

/**

592

* Create key range for all tuples with given prefix.

593

*

594

* @param prefix Tuple prefix

595

* @return Range for prefix queries

596

*/

597

public static Range range(Tuple prefix);

598

}

599

```

600

601

**Usage Example:**

602

603

```java

604

import com.apple.foundationdb.tuple.Tuple;

605

606

// Encode structured keys

607

byte[] userKey = Tuple.from("users", userId, "profile").pack();

608

byte[] scoreKey = Tuple.from("scores", gameId, timestamp, playerId).pack();

609

610

// Create ranges for prefix queries

611

Range userRange = Tuple.from("users", userId).range(); // All keys for this user

612

Range scoreRange = Tuple.from("scores", gameId).range(); // All scores for this game

613

614

// Use in transactions

615

db.run(tr -> {

616

for (KeyValue kv : tr.getRange(userRange)) {

617

Tuple key = Tuple.fromBytes(kv.getKey());

618

// Process user data

619

}

620

return null;

621

});

622

```

623

624

### Subspace Operations

625

626

Key prefix management for organizing data hierarchies.

627

628

```java { .api }

629

public class Subspace {

630

/**

631

* Create subspace with tuple prefix.

632

*

633

* @param prefix Tuple to use as prefix

634

*/

635

public Subspace(Tuple prefix);

636

637

/**

638

* Create subspace with raw prefix.

639

*

640

* @param rawPrefix Raw bytes to use as prefix

641

*/

642

public Subspace(byte[] rawPrefix);

643

644

/**

645

* Pack tuple with subspace prefix.

646

*

647

* @param tuple Tuple to pack with prefix

648

* @return Packed key with subspace prefix

649

*/

650

public byte[] pack(Tuple tuple);

651

652

/**

653

* Pack items with subspace prefix.

654

*

655

* @param items Items to pack with prefix

656

* @return Packed key with subspace prefix

657

*/

658

public byte[] pack(Object... items);

659

660

/**

661

* Unpack key and remove subspace prefix.

662

*

663

* @param key Key to unpack

664

* @return Tuple without subspace prefix

665

*/

666

public Tuple unpack(byte[] key);

667

668

/**

669

* Create range within this subspace.

670

*

671

* @param tuple Tuple suffix for range

672

* @return Range for operations within this subspace

673

*/

674

public Range range(Tuple tuple);

675

676

/**

677

* Create range for all keys in this subspace.

678

*

679

* @return Range covering entire subspace

680

*/

681

public Range range();

682

683

/**

684

* Check if key belongs to this subspace.

685

*

686

* @param key Key to check

687

* @return true if key is in this subspace

688

*/

689

public boolean contains(byte[] key);

690

691

/**

692

* Create child subspace.

693

*

694

* @param tuple Additional tuple elements for child

695

* @return New subspace with extended prefix

696

*/

697

public Subspace subspace(Tuple tuple);

698

699

/**

700

* Get the raw prefix for this subspace.

701

*

702

* @return Raw prefix bytes

703

*/

704

public byte[] getKey();

705

}

706

```

707

708

### Directory Layer

709

710

Hierarchical directory management system built on subspaces.

711

712

```java { .api }

713

public interface DirectoryLayer {

714

/**

715

* Create or open directory at path.

716

*

717

* @param tr Transaction handle

718

* @param path Directory path as list of strings

719

* @return Directory handle

720

*/

721

CompletableFuture<Directory> createOrOpen(ReadTransaction tr, List<String> path);

722

723

/**

724

* Create or open directory at path with layer.

725

*

726

* @param tr Transaction handle

727

* @param path Directory path as list of strings

728

* @param layer Layer identifier for directory type

729

* @return Directory handle

730

*/

731

CompletableFuture<Directory> createOrOpen(ReadTransaction tr, List<String> path, byte[] layer);

732

733

/**

734

* Open existing directory at path.

735

*

736

* @param tr Transaction handle

737

* @param path Directory path as list of strings

738

* @return Directory handle

739

* @throws DirectoryException if directory doesn't exist

740

*/

741

CompletableFuture<Directory> open(ReadTransaction tr, List<String> path);

742

743

/**

744

* Create new directory at path.

745

*

746

* @param tr Transaction handle

747

* @param path Directory path as list of strings

748

* @return Directory handle

749

* @throws DirectoryException if directory already exists

750

*/

751

CompletableFuture<Directory> create(Transaction tr, List<String> path);

752

753

/**

754

* List subdirectories at path.

755

*

756

* @param tr Transaction handle

757

* @param path Directory path to list

758

* @return CompletableFuture containing list of subdirectory names

759

*/

760

CompletableFuture<List<String>> list(ReadTransaction tr, List<String> path);

761

762

/**

763

* Remove directory and all its data.

764

*

765

* @param tr Transaction handle

766

* @param path Directory path to remove

767

* @return CompletableFuture containing true if directory was removed

768

*/

769

CompletableFuture<Boolean> remove(Transaction tr, List<String> path);

770

771

/**

772

* Move directory to new path.

773

*

774

* @param tr Transaction handle

775

* @param oldPath Current directory path

776

* @param newPath New directory path

777

* @return Directory handle at new location

778

*/

779

CompletableFuture<Directory> move(Transaction tr, List<String> oldPath, List<String> newPath);

780

781

/**

782

* Check if directory exists at path.

783

*

784

* @param tr Transaction handle

785

* @param path Directory path to check

786

* @return CompletableFuture containing true if directory exists

787

*/

788

CompletableFuture<Boolean> exists(ReadTransaction tr, List<String> path);

789

}

790

791

public interface Directory extends Subspace {

792

/**

793

* Directory handle extending Subspace functionality.

794

*/

795

796

/**

797

* Get the layer identifier for this directory.

798

*

799

* @return Layer identifier

800

*/

801

byte[] getLayer();

802

803

/**

804

* Get the path for this directory.

805

*

806

* @return Directory path as list of strings

807

*/

808

List<String> getPath();

809

}

810

811

// Default directory layer instance

812

public static final DirectoryLayer DEFAULT = new DirectoryLayerImpl();

813

```

814

815

### Asynchronous Utilities

816

817

Helper classes for working with asynchronous operations.

818

819

```java { .api }

820

public interface AsyncIterable<T> {

821

/**

822

* Get async iterator for this iterable.

823

*

824

* @return AsyncIterator for traversing items

825

*/

826

AsyncIterator<T> iterator();

827

828

/**

829

* Convert to list (blocking operation).

830

*

831

* @return List containing all items

832

*/

833

CompletableFuture<List<T>> asList();

834

835

/**

836

* Process each item with given function.

837

*

838

* @param consumer Function to apply to each item

839

* @return CompletableFuture that completes when all items processed

840

*/

841

CompletableFuture<Void> forEach(Function<T, CompletableFuture<Void>> consumer);

842

}

843

844

public interface AsyncIterator<T> {

845

/**

846

* Check if more items are available.

847

*

848

* @return CompletableFuture containing true if more items available

849

*/

850

CompletableFuture<Boolean> hasNext();

851

852

/**

853

* Get next item.

854

*

855

* @return CompletableFuture containing next item

856

*/

857

CompletableFuture<T> next();

858

}

859

```

860

861

## Configuration Options

862

863

### Streaming Modes

864

865

```java { .api }

866

public enum StreamingMode {

867

WANT_ALL(-2),

868

ITERATOR(-1), // Default for iteration

869

EXACT(0),

870

SMALL(1),

871

MEDIUM(2),

872

LARGE(3),

873

SERIAL(4);

874

875

private final int code;

876

877

StreamingMode(int code) {

878

this.code = code;

879

}

880

881

public int getCode() {

882

return code;

883

}

884

}

885

```

886

887

### Option Classes

888

889

```java { .api }

890

// Network options

891

public enum NetworkOptions {

892

TLS_CERT_PATH(13),

893

TLS_KEY_PATH(14),

894

TLS_VERIFY_PEERS(15),

895

EXTERNAL_CLIENT_LIBRARY(16),

896

DISABLE_LOCAL_CLIENT(17);

897

898

private final int code;

899

public int getCode() { return code; }

900

}

901

902

// Database options

903

public enum DatabaseOptions {

904

LOCATION_CACHE_SIZE(10),

905

MAX_WATCHES(20),

906

MACHINE_ID(21),

907

TRANSACTION_TIMEOUT(30),

908

TRANSACTION_RETRY_LIMIT(31);

909

910

private final int code;

911

public int getCode() { return code; }

912

}

913

914

// Transaction options

915

public enum TransactionOptions {

916

CAUSAL_READ_RISKY(20),

917

READ_YOUR_WRITES_DISABLE(22),

918

PRIORITY_SYSTEM_IMMEDIATE(200),

919

PRIORITY_BATCH(201),

920

TIMEOUT(500),

921

RETRY_LIMIT(501),

922

SIZE_LIMIT(503);

923

924

private final int code;

925

public int getCode() { return code; }

926

}

927

```

928

929

## Complete Usage Example

930

931

```java

932

import com.apple.foundationdb.*;

933

import com.apple.foundationdb.tuple.Tuple;

934

import com.apple.foundationdb.subspace.Subspace;

935

import com.apple.foundationdb.directory.DirectoryLayer;

936

import java.util.concurrent.CompletableFuture;

937

import java.util.List;

938

import java.util.ArrayList;

939

940

public class FoundationDBExample {

941

private final Database db;

942

private final Subspace userSpace;

943

private final Subspace sessionSpace;

944

945

public FoundationDBExample() {

946

// Initialize FoundationDB

947

FDB fdb = FDB.selectAPIVersion(740);

948

this.db = fdb.open();

949

950

// Set up subspaces

951

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

952

this.sessionSpace = new Subspace(Tuple.from("sessions"));

953

}

954

955

public String createUser(String userId, String email, String name) {

956

return db.run(tr -> {

957

Subspace user = userSpace.subspace(Tuple.from(userId));

958

959

// Store user profile

960

tr.set(user.pack(Tuple.from("email")), email.getBytes());

961

tr.set(user.pack(Tuple.from("name")), name.getBytes());

962

tr.set(user.pack(Tuple.from("created")),

963

String.valueOf(System.currentTimeMillis()).getBytes());

964

965

// Add to email index

966

byte[] emailKey = Tuple.from("email_index", email, userId).pack();

967

tr.set(emailKey, new byte[0]);

968

969

return userId;

970

});

971

}

972

973

public CompletableFuture<User> getUserProfileAsync(String userId) {

974

return db.readAsync(tr -> {

975

Subspace user = userSpace.subspace(Tuple.from(userId));

976

977

CompletableFuture<List<KeyValue>> rangeResult =

978

tr.getRange(user.range()).asList();

979

980

return rangeResult.thenApply(kvs -> {

981

User userObj = new User(userId);

982

for (KeyValue kv : kvs) {

983

Tuple key = user.unpack(kv.getKey());

984

String field = (String) key.get(0);

985

String value = new String(kv.getValue());

986

987

switch (field) {

988

case "email":

989

userObj.setEmail(value);

990

break;

991

case "name":

992

userObj.setName(value);

993

break;

994

case "created":

995

userObj.setCreatedTime(Long.parseLong(value));

996

break;

997

}

998

}

999

return userObj;

1000

});

1001

}).thenCompose(future -> future);

1002

}

1003

1004

public String findUserByEmail(String email) {

1005

return db.read(tr -> {

1006

Range emailRange = Tuple.from("email_index", email).range();

1007

1008

for (KeyValue kv : tr.getRange(emailRange)) {

1009

Tuple key = Tuple.fromBytes(kv.getKey());

1010

// Extract user_id from key

1011

return (String) key.get(2); // email_index, email, user_id

1012

}

1013

1014

return null;

1015

});

1016

}

1017

1018

public List<String> getAllUsers() {

1019

return db.read(tr -> {

1020

List<String> userIds = new ArrayList<>();

1021

1022

for (KeyValue kv : tr.getRange(userSpace.range())) {

1023

Tuple key = userSpace.unpack(kv.getKey());

1024

if (key.size() >= 2) {

1025

String userId = (String) key.get(0);

1026

String field = (String) key.get(1);

1027

1028

// Only add user ID once (when we see any field)

1029

if ("email".equals(field) && !userIds.contains(userId)) {

1030

userIds.add(userId);

1031

}

1032

}

1033

}

1034

1035

return userIds;

1036

});

1037

}

1038

1039

public void close() {

1040

// FoundationDB handles cleanup automatically

1041

// No explicit close needed for database handles

1042

}

1043

1044

// Helper class

1045

public static class User {

1046

private String id;

1047

private String email;

1048

private String name;

1049

private long createdTime;

1050

1051

public User(String id) {

1052

this.id = id;

1053

}

1054

1055

// Getters and setters

1056

public String getId() { return id; }

1057

public String getEmail() { return email; }

1058

public void setEmail(String email) { this.email = email; }

1059

public String getName() { return name; }

1060

public void setName(String name) { this.name = name; }

1061

public long getCreatedTime() { return createdTime; }

1062

public void setCreatedTime(long createdTime) { this.createdTime = createdTime; }

1063

}

1064

1065

public static void main(String[] args) {

1066

FoundationDBExample example = new FoundationDBExample();

1067

1068

try {

1069

// Create user

1070

String userId = example.createUser("user123", "alice@example.com", "Alice Smith");

1071

System.out.println("Created user: " + userId);

1072

1073

// Get user profile (async)

1074

CompletableFuture<User> userFuture = example.getUserProfileAsync(userId);

1075

User user = userFuture.join();

1076

System.out.println("User profile: " + user.getName() + " (" + user.getEmail() + ")");

1077

1078

// Find user by email

1079

String foundUserId = example.findUserByEmail("alice@example.com");

1080

System.out.println("Found user by email: " + foundUserId);

1081

1082

// List all users

1083

List<String> allUsers = example.getAllUsers();

1084

System.out.println("All users: " + allUsers);

1085

1086

} finally {

1087

example.close();

1088

}

1089

}

1090

}

1091

```