Demonstrates using the API Executor and Streaming API Executor to call OpenFGA endpoints that are not yet wrapped by the SDK.
Both executors give you direct HTTP access to OpenFGA endpoints. ApiExecutor goes through the full SDK pipeline (authentication, retries, error handling, telemetry).
StreamingApiExecutor applies authentication and configured timeouts/headers but not retries or telemetry.
ApiExecutor |
StreamingApiExecutor |
|
|---|---|---|
| For | Endpoints returning a single JSON response | Streaming endpoints |
| Returns | CompletableFuture<ApiResponse<T>> |
CompletableFuture<Void> + per-object consumer callback |
| Access | client.apiExecutor() |
client.streamingApiExecutor(MyResponse.class) |
Use cases:
- Calling endpoints not yet supported by the SDK
- Using an SDK version that lacks support for a particular endpoint
- Accessing custom or experimental endpoints that extend the OpenFGA API
- Java 17 or higher
- OpenFGA server running on
http://localhost:8080(or setFGA_API_URL)
# Start OpenFGA server first (if not already running)
docker run -p 8080:8080 openfga/openfga run
# From the SDK root directory, build the SDK
./gradlew build
# Run the standard (non-streaming) example
cd examples/api-executor
make run
# Run the streaming example
make run-streamingDemonstrates ApiExecutor against real OpenFGA endpoints:
- List Stores (GET, typed response) — deserializes into
ListStoresResponse - Get Store (GET, raw JSON) — returns the raw JSON string
- List Stores with Pagination — demonstrates query parameters
- Create Store (POST, custom headers) — custom HTTP headers
- Error Handling — handles a 404 gracefully
Demonstrates StreamingApiExecutor against the streamed-list-objects endpoint:
- Creates a temporary store and writes an authorization model
- Writes 200 relationship tuples (100 owners + 100 viewers)
- Calls
POST /stores/{store_id}/streamed-list-objectsviaclient.streamingApiExecutor(StreamedListObjectsResponse.class).stream(request, consumer) - Receives each object via a consumer callback as it arrives
- Cleans up the store
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.POST, "/stores/{store_id}/custom-endpoint")
.pathParam("store_id", storeId)
.queryParam("page_size", "20")
.body(requestBody)
.header("X-Custom-Header", "value")
.build();
// Typed response
ApiResponse<CustomResponse> response = client.apiExecutor().send(request, CustomResponse.class).get();
// Raw JSON
ApiResponse<String> raw = client.apiExecutor().send(request).get();ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.POST, "/stores/{store_id}/streamed-endpoint")
.body(requestBody)
.build();
client.streamingApiExecutor(MyStreamedResponse.class)
.stream(
request,
response -> System.out.println("Got: " + response), // per-object callback
error -> System.err.println("Error: " + error) // optional error callback
)
.thenRun(() -> System.out.println("Stream complete"));If your response type is itself generic, use the TypeReference overload:
TypeReference<StreamResult<MyStreamedResponse>> typeRef = new TypeReference<StreamResult<MyStreamedResponse>>() {};
client.streamingApiExecutor(typeRef).stream(request, consumer);ApiExecutor requests go through the full SDK pipeline:
- Authentication credentials
- Retry logic for 5xx errors with exponential backoff
- Error handling and exception mapping
- Configured timeouts and headers
- Telemetry hooks
- Automatic
{store_id}substitution from client configuration
StreamingApiExecutor requests use direct HTTP streaming. Authentication and configured timeouts/headers are applied, but retries and telemetry are not.
ApiExecutorExample.java— standard (non-streaming) API Executor usageStreamingApiExecutorExample.java— streaming API Executor usage via thestreamed-list-objectsendpoint