Skip to content

Java

Our API Reference has generic examples in every supported language, and we strive to make the experience of each SDK very similar. However, there are some things specific to the Java SDK that we want to call out here.

The Java SDK requires Java 11 or higher and uses modern Java features including CompletableFuture for asynchronous operations.

The SDK can be installed via Maven or Gradle:

Maven:

<dependency>
<groupId>cloud.stately</groupId>
<artifactId>statelydb</artifactId>
<version>LATEST</version>
</dependency>

Gradle:

dependencies {
implementation 'cloud.stately:statelydb:+'
}

The generated code uses the Builder pattern for creating item objects. Each item type has a Builder class that requires all mandatory fields in the constructor and provides optional field setters:

import schema.User;
import schema.Movie;
// Create a User with all required fields
User user = new User.Builder(
"Jane Doe", // displayName
"jane.doe@example.com", // email
Instant.now().getEpochSecond(), // lastLoginDate
1L // numLogins
).build();
// Create a Movie with all required fields
Movie movie = new Movie.Builder(
"Drama", // genre
1994L, // year
"Forrest Gump", // title
142L, // duration
"PG-13" // rating
).build();

All client operations return CompletableFuture<T> objects for asynchronous execution:

// Asynchronous put operation
CompletableFuture<StatelyItem> putResult = client.put(user);
// Block and get the result
User result = putResult.<User>get();
// Or handle asynchronously
putResult.thenAccept(item -> {
System.out.println("Saved user: " + item.getDisplayName());
});

The generated types include a primaryKeyPath() method that returns the primary key path for an item:

User user = // ... create user
String keyPath = user.primaryKeyPath(); // e.g., "/usr-12345678-1234-1234-1234-123456789012"
// For manual key path construction, convert UUIDs to strings
UUID userId = user.getId();
String manualKeyPath = "/usr-" + userId.toString();

Many client APIs return a StatelyItem, but you want to know exactly what type it is. You can use Java’s instanceof operator and casting for this:

if (item instanceof Movie) {
Movie movie = (Movie) item;
// it's a movie
}
// Or with pattern matching (Java 16+)
switch (item) {
case Movie movie -> {
// it's a movie
}
case User user -> {
// it's a user
}
default -> {
// unknown type
}
}

StatelyDB-specific errors are wrapped in StatelyException which provides both gRPC and Stately error codes:

import cloud.stately.statelydb.common.StatelyException;
try {
client.get("/item-nonexistent").get();
} catch (Exception e) {
if (e.getCause() instanceof StatelyException) {
StatelyException statelyError = (StatelyException) e.getCause();
System.out.println("StatelyDB error: " + statelyError.getMessage());
System.out.println("Stately code: " + statelyError.getStatelyCode());
System.out.println("gRPC code: " + statelyError.getGrpcCode());
} else {
System.out.println("Other error: " + e.getMessage());
}
}

The client requires a ScheduledExecutorService for background operations (primarily auth token refresh). Always shut down the client and executor when done:

import schema.ClientBuilder;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
try {
Client client = new ClientBuilder(storeId, scheduler).build();
// Use the client...
} finally {
client.close();
scheduler.shutdown();
}

Transactions use a handler pattern where you provide a lambda that receives a transaction context:

import cloud.stately.statelydb.TransactionResult;
TransactionResult result = client.transaction(txn -> {
// Perform multiple operations within the transaction
return txn.put(item1)
.thenCompose(putResult -> txn.put(item2))
.thenCompose(putResult -> txn.delete("/another-keypath"));
}).get();
if (result.isCommitted()) {
System.out.println("Transaction successful");
System.out.println("Items created: " + result.getPuts().size());
}