ctx = new HashMap<>();
+ctx.put("role", "admin");
+ctx.put("department", "finance");
+ctx.put("user_id", "user_12345");
+
+BearerToken token = BearerToken.builder()
+ .setCredentials(new File(filePath))
+ .setCtx(ctx)
+ .build();
+```
+
+With the map above, your Skyflow policies can reference `request.context.role`, `request.context.department`, and `request.context.user_id` to make conditional access decisions.
+
+Context map keys must contain only alphanumeric characters and underscores (`[a-zA-Z0-9_]`). An invalid key throws a `SkyflowException` when the builder runs.
+
+[Full example](https://github.com/skyflowapi/skyflow-java/blob/main/flowvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationWithContextExample.java)
+
+## Generate scoped bearer tokens
+
+A service account with multiple roles can generate bearer tokens limited to specific roles by passing their role IDs. This is useful for services with several responsibilities, such as separating billing access from analytics access. The generated tokens are valid for 60 minutes and can only execute operations permitted by the designated roles.
+
+[Example](https://github.com/skyflowapi/skyflow-java/blob/main/flowvault/samples/src/main/java/com/example/serviceaccount/ScopedTokenGenerationExample.java):
+
+```java
+import com.skyflow.errors.SkyflowException;
+import com.skyflow.serviceaccount.util.BearerToken;
+
+import java.io.File;
+import java.util.ArrayList;
+
+/**
+ * This example demonstrates how to generate a Scoped Bearer Token in two ways:
+ * 1. Using a credentials file specified by its file path.
+ * 2. Using the credentials as a string.
+ *
+ * Scoped tokens are generated by assigning specific roles for access control.
+ */
+public class ScopedTokenGenerationExample {
+ public static void main(String[] args) {
+ String scopedToken = null; // Variable to store the generated Scoped Bearer Token
+
+ // Example 1: Generate Scoped Token using a credentials file path
+ try {
+ // Step 1: Specify the roles required for the scoped token
+ ArrayList roles = new ArrayList<>();
+ roles.add("YOUR_ROLE_ID"); // Replace with your actual role ID
+
+ // Step 2: Specify the path to the credentials file
+ String filePath = ""; // Replace with the actual file path
+
+ // Step 3: Create a BearerToken object using the file path and roles
+ BearerToken bearerToken = BearerToken.builder()
+ .setCredentials(new File(filePath)) // Provide the credentials file
+ .setRoles(roles) // Set the roles for the scoped token
+ .build();
+
+ // Step 4: Generate and print the Scoped Bearer Token
+ scopedToken = bearerToken.getBearerToken();
+ System.out.println("Scoped Token (using file path): " + scopedToken);
+ } catch (SkyflowException e) { // Handle exceptions during token generation
+ System.out.println("Error occurred while generating Scoped Token using file path:");
+ e.printStackTrace();
+ }
+
+ // Example 2: Generate Scoped Token using credentials as a string
+ try {
+ // Step 1: Specify the roles required for the scoped token
+ ArrayList roles = new ArrayList<>();
+ roles.add("YOUR_ROLE_ID"); // Replace with your actual role ID
+
+ // Step 2: Specify the credentials as a string (file contents)
+ String fileContents = ""; // Replace with actual file contents
+
+ // Step 3: Create a BearerToken object using the credentials string and roles
+ BearerToken bearerToken = BearerToken.builder()
+ .setCredentials(fileContents) // Provide the credentials as a string
+ .setRoles(roles) // Set the roles for the scoped token
+ .build();
+
+ // Step 4: Generate and print the Scoped Bearer Token
+ scopedToken = bearerToken.getBearerToken();
+ System.out.println("Scoped Token (using credentials string): " + scopedToken);
+ } catch (SkyflowException e) { // Handle exceptions during token generation
+ System.out.println("Error occurred while generating Scoped Token using credentials string:");
+ e.printStackTrace();
+ }
+ }
+}
+```
+
+Notes:
+
+- `setCredentials` accepts either the path to a service account credentials file or the credentials JSON as a string.
+- If both a file and a string are provided, the last call wins.
+- To generate bearer tokens concurrently from several threads, see [BearerTokenGenerationUsingThreadsExample](https://github.com/skyflowapi/skyflow-java/blob/main/flowvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationUsingThreadsExample.java).
+
+## Generate signed data tokens
+
+Skyflow generates data tokens when sensitive data is inserted into the vault. Those data tokens can be digitally signed with the private key of the service account credentials, which adds a further layer of protection. A signed token can only be detokenized by passing it together with a bearer token generated from service account credentials that hold the matching context and permissions.
+
+`setCtx()` on `SignedDataTokens.builder()` accepts either a **String** or a **`Map`**, in the same format as bearer tokens:
+
+```java
+import com.skyflow.serviceaccount.util.SignedDataTokenResponse;
+import com.skyflow.serviceaccount.util.SignedDataTokens;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+ArrayList dataTokens = new ArrayList<>();
+dataTokens.add("");
+
+// String context
+SignedDataTokens signedToken = SignedDataTokens.builder()
+ .setCredentials(new File(filePath))
+ .setCtx("user_12345")
+ .setTimeToLive(30) // seconds
+ .setDataTokens(dataTokens)
+ .build();
+
+// JSON object context
+Map ctx = new HashMap<>();
+ctx.put("role", "analyst");
+ctx.put("department", "research");
+
+SignedDataTokens signedTokenWithMap = SignedDataTokens.builder()
+ .setCredentials(new File(filePath))
+ .setCtx(ctx)
+ .setTimeToLive(30)
+ .setDataTokens(dataTokens)
+ .build();
+
+List signed = signedToken.getSignedDataTokens();
+for (SignedDataTokenResponse entry : signed) {
+ System.out.println(entry.getToken() + " -> " + entry.getSignedToken());
+}
```
-`getBearerToken()` caches the token and only mints a new one once the current one has expired, so it is safe to call per request.
+[Full example](https://github.com/skyflowapi/skyflow-java/blob/main/flowvault/samples/src/main/java/com/example/serviceaccount/SignedTokenGenerationExample.java)
-## Context-aware and scoped tokens
+Response:
-`BearerToken.builder()` also accepts `setCtx(String | Map)` for context-aware authorization and `setRoles(ArrayList)` for scoped tokens. Signed data tokens are available through `com.skyflow.serviceaccount.util.SignedDataTokens`. These utilities are identical to skyvault's — see [Authenticate with bearer tokens](../skyvault/README.md#authenticate-with-bearer-tokens) for worked examples of every variant.
+```json
+[
+ {
+ "dataToken": "5530-4316-0674-5748",
+ "signedDataToken": "signed_token_eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJzLCpZjA"
+ }
+]
+```
+
+Notes:
+
+- `setCredentials` accepts either the path to a service account credentials file or the credentials JSON as a string. If both are provided, the last call wins.
+- `setTimeToLive` is in seconds and defaults to 60.
# Initialize the client
@@ -294,6 +539,11 @@ Skyflow skyflowClient = Skyflow.builder()
The SDK logs through `java.util.logging` at `LogLevel.ERROR` by default. Levels rank `DEBUG` < `INFO` < `WARN` < `ERROR` < `OFF`; setting a level prints that level and everything above it. Change it with `Skyflow.builder().setLogLevel(LogLevel.DEBUG)`.
+## Concurrency, thread safety, and resource lifecycle
+
+- **Thread safety** — `Skyflow` and every `VaultController` it hands out are safe to share and call concurrently across threads, which is exactly how [Initialize the client](#initialize-the-client) expects them to be used: build once, reuse for the app's lifetime.
+- **Async execution** — each `*Async` call runs its batches on a small dedicated thread pool sized to that call's concurrency limit (see [Batching and concurrency](#batching-and-concurrency)), scoped to the call rather than shared globally.
+
# VaultController — Bulk operations
`VaultController` is returned by `skyflowClient.vault()`. `flowvault` exposes these bulk vault operations:
@@ -419,8 +669,11 @@ Insert many records — even across different tables — in a single call. Each
### Construct a bulk insert request
+Continuing from [Quickstart](#quickstart) — `skyflowClient` below is the `Skyflow` client built there.
+
```java
import com.skyflow.errors.SkyflowException;
+import com.skyflow.vault.controller.VaultController;
import com.skyflow.vault.data.BulkInsertRequest;
import com.skyflow.vault.data.BulkInsertRequestRecord;
import com.skyflow.vault.data.BulkInsertResponse;
@@ -435,6 +688,9 @@ import java.util.Map;
public class BulkInsertExample {
public static void main(String[] args) throws SkyflowException {
+ // skyflowClient is the Skyflow client built in Quickstart
+ VaultController vault = skyflowClient.vault();
+
// Step 1: Build each record. Here tableName lives on the records, so each one carries it.
Map record1Data = new HashMap<>();
record1Data.put("card_number", "4111111111111111");
@@ -489,6 +745,8 @@ BulkInsertRequest insertRequest = BulkInsertRequest.builder()
```java
import java.util.concurrent.CompletableFuture;
+VaultController vault = skyflowClient.vault(); // skyflowClient from Quickstart
+
CompletableFuture future = vault.bulkInsertAsync(insertRequest);
future.thenAccept(response -> System.out.println(response));
```
@@ -563,8 +821,11 @@ Tokenize many values in one call. Each value can be tokenized against one or mor
### Construct a bulk tokenize request
+Continuing from [Quickstart](#quickstart) — `skyflowClient` below is the `Skyflow` client built there.
+
```java
import com.skyflow.errors.SkyflowException;
+import com.skyflow.vault.controller.VaultController;
import com.skyflow.vault.data.BulkTokenizeRequest;
import com.skyflow.vault.data.BulkTokenizeRequestRecord;
import com.skyflow.vault.data.BulkTokenizeResponse;
@@ -575,6 +836,9 @@ import java.util.List;
public class BulkTokenizeExample {
public static void main(String[] args) throws SkyflowException {
+ // skyflowClient is the Skyflow client built in Quickstart
+ VaultController vault = skyflowClient.vault();
+
BulkTokenizeRequestRecord record1 = BulkTokenizeRequestRecord.builder()
.value("4111111111111111")
.tokenGroupNames(Arrays.asList("card_number_cg"))
@@ -604,6 +868,8 @@ public class BulkTokenizeExample {
### Async bulk tokenize
```java
+VaultController vault = skyflowClient.vault(); // skyflowClient from Quickstart
+
CompletableFuture future = vault.bulkTokenizeAsync(tokenizeRequest);
```
@@ -639,8 +905,11 @@ Detokenize many tokens in one call, optionally overriding the redaction applied
### Construct a bulk detokenize request
+Continuing from [Quickstart](#quickstart) — `skyflowClient` below is the `Skyflow` client built there.
+
```java
import com.skyflow.errors.SkyflowException;
+import com.skyflow.vault.controller.VaultController;
import com.skyflow.vault.data.BulkDetokenizeRequest;
import com.skyflow.vault.data.BulkDetokenizeResponse;
import com.skyflow.vault.data.TokenGroupRedactions;
@@ -651,13 +920,14 @@ import java.util.List;
public class BulkDetokenizeExample {
public static void main(String[] args) throws SkyflowException {
+ // skyflowClient is the Skyflow client built in Quickstart
+ VaultController vault = skyflowClient.vault();
+
List tokens = new ArrayList<>(Arrays.asList(
"5479-4229-4622-1393",
"a1b2c3d4-e5f6-7890-abcd-ef1234567890"
));
- // redaction is a free-form string understood by the vault (e.g. "PLAIN_TEXT",
- // "MASKED", "REDACTED", "DEFAULT" — the same redaction types as skyvault's RedactionType enum)
TokenGroupRedactions redaction = TokenGroupRedactions.builder()
.tokenGroupName("card_number_cg")
.redaction("MASKED")
@@ -677,6 +947,8 @@ public class BulkDetokenizeExample {
### Async bulk detokenize
```java
+VaultController vault = skyflowClient.vault(); // skyflowClient from Quickstart
+
CompletableFuture future = vault.bulkDetokenizeAsync(detokenizeRequest);
```
@@ -730,8 +1002,11 @@ Delete many tokens in one call.
### Construct a bulk delete tokens request
+Continuing from [Quickstart](#quickstart) — `skyflowClient` below is the `Skyflow` client built there.
+
```java
import com.skyflow.errors.SkyflowException;
+import com.skyflow.vault.controller.VaultController;
import com.skyflow.vault.data.BulkDeleteTokensRequest;
import com.skyflow.vault.data.BulkDeleteTokensResponse;
@@ -741,6 +1016,9 @@ import java.util.List;
public class BulkDeleteTokensExample {
public static void main(String[] args) throws SkyflowException {
+ // skyflowClient is the Skyflow client built in Quickstart
+ VaultController vault = skyflowClient.vault();
+
List tokens = new ArrayList<>(Arrays.asList(
"5479-4229-4622-1393",
"a1b2c3d4-e5f6-7890-abcd-ef1234567890"
@@ -759,6 +1037,8 @@ public class BulkDeleteTokensExample {
### Async bulk delete tokens
```java
+VaultController vault = skyflowClient.vault(); // skyflowClient from Quickstart
+
CompletableFuture future = vault.bulkDeleteTokensAsync(deleteTokensRequest);
```
@@ -800,8 +1080,11 @@ Insert records in a single API call — the unary counterpart of [Bulk Insert](#
### Construct an insert request
+Continuing from [Quickstart](#quickstart) — `skyflowClient` below is the `Skyflow` client built there.
+
```java
import com.skyflow.errors.SkyflowException;
+import com.skyflow.vault.controller.VaultController;
import com.skyflow.vault.data.InsertRequest;
import com.skyflow.vault.data.InsertRequestRecord;
import com.skyflow.vault.data.InsertResponse;
@@ -814,6 +1097,9 @@ import java.util.Map;
public class InsertExample {
public static void main(String[] args) throws SkyflowException {
+ // skyflowClient is the Skyflow client built in Quickstart
+ VaultController vault = skyflowClient.vault();
+
// Step 1: Build each record. Here tableName lives on the request, so no record carries it.
Map recordData = new HashMap<>();
recordData.put("card_number", "4111111111111111");
@@ -898,8 +1184,11 @@ Detokenize tokens in a single API call — the unary counterpart of [Bulk Detoke
### Construct a detokenize request
+Continuing from [Quickstart](#quickstart) — `skyflowClient` below is the `Skyflow` client built there.
+
```java
import com.skyflow.errors.SkyflowException;
+import com.skyflow.vault.controller.VaultController;
import com.skyflow.vault.data.DetokenizeRequest;
import com.skyflow.vault.data.DetokenizeResponse;
import com.skyflow.vault.data.DetokenizeResponseRecord;
@@ -911,13 +1200,14 @@ import java.util.List;
public class DetokenizeExample {
public static void main(String[] args) throws SkyflowException {
+ // skyflowClient is the Skyflow client built in Quickstart
+ VaultController vault = skyflowClient.vault();
+
List tokens = new ArrayList<>(Arrays.asList(
"5479-4229-4622-1393",
"a1b2c3d4-e5f6-7890-abcd-ef1234567890"
));
- // redaction is a free-form string understood by the vault (e.g. "PLAIN_TEXT",
- // "MASKED", "REDACTED", "DEFAULT")
TokenGroupRedactions redaction = TokenGroupRedactions.builder()
.tokenGroupName("card_number_cg")
.redaction("MASKED")
@@ -995,8 +1285,11 @@ Read records back from a table, by skyflow ID or by unique value, optionally ove
### Construct a get request
+Continuing from [Quickstart](#quickstart) — `skyflowClient` below is the `Skyflow` client built there.
+
```java
import com.skyflow.errors.SkyflowException;
+import com.skyflow.vault.controller.VaultController;
import com.skyflow.vault.data.ColumnRedactions;
import com.skyflow.vault.data.GetRequest;
import com.skyflow.vault.data.GetResponse;
@@ -1008,6 +1301,9 @@ import java.util.Collections;
public class GetExample {
public static void main(String[] args) throws SkyflowException {
+ // skyflowClient is the Skyflow client built in Quickstart
+ VaultController vault = skyflowClient.vault();
+
// Step 1: Optionally override how individual columns come back. Anything not listed
// uses the redaction configured on the vault's policy.
ColumnRedactions redaction = ColumnRedactions.builder()
@@ -1118,8 +1414,11 @@ Update records in a table by skyflow ID, in a single API call.
### Construct an update request
+Continuing from [Quickstart](#quickstart) — `skyflowClient` below is the `Skyflow` client built there.
+
```java
import com.skyflow.errors.SkyflowException;
+import com.skyflow.vault.controller.VaultController;
import com.skyflow.vault.data.UpdateRequest;
import com.skyflow.vault.data.UpdateRequestRecord;
import com.skyflow.vault.data.UpdateResponse;
@@ -1131,6 +1430,9 @@ import java.util.Map;
public class UpdateExample {
public static void main(String[] args) throws SkyflowException {
+ // skyflowClient is the Skyflow client built in Quickstart
+ VaultController vault = skyflowClient.vault();
+
// Step 1: Build the record — the columns to change, keyed by the record's skyflow ID
Map data = new HashMap<>();
data.put("cardholder_name", "jane doe");
@@ -1206,8 +1508,11 @@ Delete records from a table by skyflow ID or unique value, in a single API call.
### Construct a delete request
+Continuing from [Quickstart](#quickstart) — `skyflowClient` below is the `Skyflow` client built there.
+
```java
import com.skyflow.errors.SkyflowException;
+import com.skyflow.vault.controller.VaultController;
import com.skyflow.vault.data.DeleteRequest;
import com.skyflow.vault.data.DeleteResponse;
import com.skyflow.vault.data.DeleteResponseRecord;
@@ -1218,6 +1523,9 @@ import java.util.List;
public class DeleteExample {
public static void main(String[] args) throws SkyflowException {
+ // skyflowClient is the Skyflow client built in Quickstart
+ VaultController vault = skyflowClient.vault();
+
List ids = new ArrayList<>(Arrays.asList(
"9fac9201-7b8a-4446-93f8-5244e1213bd1",
"b2308e2a-c1f5-469b-97b7-1f193159399b"
@@ -1274,6 +1582,8 @@ To include custom HTTP headers on an outgoing request — bulk or unary — pass
import com.skyflow.enums.CustomHeaderKey;
import com.skyflow.vault.data.BulkInsertOptions;
+VaultController vault = skyflowClient.vault(); // skyflowClient from Quickstart
+
BulkInsertOptions options = BulkInsertOptions.builder()
.interceptor(context -> context.addHeader(CustomHeaderKey.REQUEST_ID_HEADER, ""))
.build();
@@ -1339,6 +1649,8 @@ A unary response has no summary and no `getIndex()` — just `getRecords()`, in
The idiomatic way to consume a bulk response:
```java
+VaultController vault = skyflowClient.vault(); // skyflowClient from Quickstart
+
BulkInsertResponse response = vault.bulkInsert(insertRequest);
System.out.println("inserted " + response.getSummary().getTotalInserted()
@@ -1376,6 +1688,8 @@ for (BulkTokenizeResponseRecord record : tokenizeResponse.getRecords()) {
```java
import com.skyflow.errors.SkyflowException;
+VaultController vault = skyflowClient.vault(); // skyflowClient from Quickstart
+
try {
BulkInsertResponse response = vault.bulkInsert(insertRequest);
// reaching here means the CALL succeeded — individual records may still have failed
@@ -1432,6 +1746,8 @@ Because failures are reported per record, a partial failure can be retried witho
| `BulkDeleteTokensResponse` | `getTokensToRetry()` | `List` — the tokens to resubmit |
```java
+VaultController vault = skyflowClient.vault(); // skyflowClient from Quickstart
+
BulkInsertResponse response = vault.bulkInsert(insertRequest);
List retryable = response.getRecordsToRetry();
@@ -1439,6 +1755,7 @@ if (!retryable.isEmpty()) {
BulkInsertResponse retryResponse = vault.bulkInsert(
BulkInsertRequest.builder()
.tableName("table1")
+ .upsert(UpsertOptions.builder().uniqueColumns(Arrays.asList("email")).build())
.records(new ArrayList<>(retryable))
.build());
}
diff --git a/flowvault/samples/README.md b/flowvault/samples/README.md
index 5f62a8fc..c1321338 100644
--- a/flowvault/samples/README.md
+++ b/flowvault/samples/README.md
@@ -1,7 +1,7 @@
# FlowVault Java SDK samples
Runnable samples for the `flowvault` module (`com.skyflow:skyflow-flowvault-java`) — bulk vault
-operations against a Flow DB vault, plus the shared service-account/bearer-token utilities.
+operations against Flow Vault, plus the shared service-account/bearer-token utilities.
See the [flowvault README](../README.md) for the full API reference these samples exercise.