Version 0.1.0-alpha.1 · canonical source: iRoute repository

iRoute Java SDK

dev.iroute:iroute-sdk is a dependency-free Java client for the iRoute v1 HTTP and SSE API. It uses the JDK HTTP client and deliberately returns JSON strings so applications can choose their own JSON library.

It supports execution, polling, cancellation, approvals, artifacts, gateway health, observability, and buffered event replay. Routing and provider policy remain server-side.

Status and requirements

  • SDK version: 0.1.0-alpha.1
  • Java baseline: Java 25 LTS
  • Maven coordinates: dev.iroute:iroute-sdk
  • Runtime dependencies: JDK only

Once published, the public alpha is installed from Maven Central as a prerelease artifact. The source installation below remains available.

Start iRoute

docker compose --file deploy/compose.sqlite.yaml up --build --wait

The default API URL is http://localhost:8080. See the shared SDK usage guide for source-run and authentication options.

Install from Maven Central

<dependency>
  <groupId>dev.iroute</groupId>
  <artifactId>iroute-sdk</artifactId>
  <version>0.1.0-alpha.1</version>
</dependency>

Install from source

Build and install the SDK into the local Maven repository:

mvn --file ../iRoute/sdks/java/pom.xml clean install

Then add it to the application:

<dependency>
  <groupId>dev.iroute</groupId>
  <artifactId>iroute-sdk</artifactId>
  <version>0.1.0-alpha.1</version>
</dependency>

Create a client

import dev.iroute.IRouteClient;
import java.net.URI;
import java.time.Duration;
import java.util.List;
import java.util.Map;

var environment = System.getenv();
var client = new IRouteClient(
    URI.create(environment.getOrDefault("IROUTE_URL", "http://localhost:8080")),
    new IRouteClient.Options(
        environment.get("IROUTE_TOKEN"),
        environment.getOrDefault("IROUTE_TENANT", "demo"),
        environment.getOrDefault("IROUTE_ACTOR", "java-app"),
        List.of(),
        Duration.ofSeconds(30)));

In Development the identity options become headers. In JWT mode the server derives identity and scopes from token claims and will not allow headers to elevate them.

JSON representation

The SDK does not impose Jackson, Gson, JSON-P, or another JSON dependency. Methods ending in Json accept or return complete JSON strings. The examples below use json.readTree(...) to mean the parser already chosen by the application. With Jackson, json would be an ObjectMapper.

Submit an execution

import java.util.UUID;

var idempotencyKey = "java-email-" + UUID.randomUUID();
var request = """
    {
      "taskType": "email.draft",
      "input": {"purpose": "Confirm the Java SDK integration."},
      "idempotencyKey": "%s",
      "constraints": {
        "maxOutputTokens": 500,
        "deadlineMilliseconds": 30000,
        "minimumQuality": 0.8
      }
    }
    """.formatted(idempotencyKey);

var executionJson = client.executeJson(request, idempotencyKey);
System.out.println(executionJson);

Keep the same idempotency key when retrying the same logical request.

Poll until terminal

Parse executionId and status with the application's JSON library:

var execution = json.readTree(executionJson);
var executionId = execution.required("executionId").asText();
var terminal = java.util.Set.of("Succeeded", "Failed", "Cancelled", "TimedOut");

while (!terminal.contains(execution.required("status").asText())) {
    Thread.sleep(500);
    var current = client.getJson(executionId)
        .orElseThrow(() -> new IllegalStateException(
            "Execution is not visible to this tenant."));
    execution = json.readTree(current);
}

if (execution.required("status").asText().equals("Succeeded")) {
    System.out.println(execution.required("outcome").required("output"));
} else {
    System.err.println(execution.path("error"));
}

WaitingForApproval is non-terminal.

Read event replay

long lastSequence = 0;
var eventJsonValues = client.streamEventsJson(executionId, lastSequence);

for (var eventJson : eventJsonValues) {
    var event = json.readTree(eventJson);
    lastSequence = event.required("sequence").asLong();
    System.out.println(lastSequence + " " + event.required("type").asText());
}

The default Java transport uses BodyHandlers.ofString(). It buffers the server response and returns List<String> after the stream closes; it is suitable for terminal replay and bounded local use, not an indefinitely open live feed. Applications requiring incremental events should provide an incremental transport or consume the SSE endpoint with their HTTP stack. Persist the last sequence for reconnects and ignore unknown event fields.

Cancel an execution

var accepted = client.cancel(executionId);
if (!accepted) System.out.println("Execution was not found.");

Poll or replay events to confirm the durable terminal result.

Approve or deny an action

Read actionId from an approval.required event:

var decision = """
    {"actionId":"%s","approved":true,"reason":"Reviewed by an operator"}
    """.formatted(actionId);

var approvalResultJson = client.submitApprovalJson(executionId, decision);

The deciding token needs approval:grant and the action's required scope.

Retrieve an artifact

var artifactJson = client.getArtifactJson(artifactId);
artifactJson.ifPresent(System.out::println);

Not found returns Optional.empty(), including for cross-tenant access.

Health and observability

var healthJson = client.getModelGatewayHealthJson();

var summaryJson = client.getObservabilitySummaryJson(Map.of(
    "from", "2026-08-01T00:00:00Z",
    "to", "2026-08-02T00:00:00Z",
    "taskType", "email.draft"));

var timelineJson = client.getExecutionTimelineJson(executionId);

Gateway health returns JSON for HTTP 200 and 503. Summary query keys are from, to, taskType, and policyVersion. Observability is tenant scoped, and reported cost has no currency in v1.

Errors and transport timeouts

try {
    executionJson = client.executeJson(request, idempotencyKey);
} catch (IRouteClient.IRouteApiException error) {
    System.err.println(error.status() + " " + error.code());
    System.err.println(error.detail());
    System.err.println(error.responseBody());
}

IOException covers transport failures and InterruptedException preserves thread interruption. Configure the JDK request timeout with Options.timeout. The SDK does not retry automatically.

Custom transport

The constructor overload accepting IRouteClient.Transport supports tests, custom TLS configuration, proxies, metrics, or another HTTP stack. A transport receives an immutable IRouteClient.Request and must return an IRouteClient.Response; SDK error mapping remains unchanged.

Method reference

MethodResult
executeJson(requestJson, idempotencyKey?)Execution JSON
getJson(executionId)Optional execution JSON
cancel(executionId)false when not found
submitApprovalJson(executionId, decisionJson)Approval result JSON
streamEventsJson(executionId, afterSequence)Buffered event JSON list
getArtifactJson(artifactId)Optional artifact JSON
getModelGatewayHealthJson()Health JSON
getObservabilitySummaryJson(query)Summary JSON
getExecutionTimelineJson(executionId)Optional timeline JSON

Run the example and tests

From the repository root:

cd sdks/java
./run-example.sh
./run-conformance.sh

Public wire shapes are defined by the OpenAPI document and JSON Schemas.