Java SDK
A Java client for the OGuardAI API built on java.net.http, with fail-closed guarded calls, sessions, and streaming
The oguardai-sdk artifact provides a Java client for the OGuardAI API. It is built on java.net.http with Gson for JSON, has no other runtime dependency, and enforces the runtime's trust boundary on the client side.
Install
<dependency>
<groupId>com.oronts</groupId>
<artifactId>oguardai-sdk</artifactId>
<version>0.1.0</version>
</dependency>Requires Java 11 or newer.
Transform and Rehydrate
OGuardAIClient client = OGuardAIClient.builder()
.baseUrl("http://localhost:3000")
.apiKey(System.getenv("GUARDAI_API_KEY")) // only if the server runs with auth
.build();
TransformResponse result = client.transform(new TransformRequest()
.input("Contact Julia Schneider at julia@example.com")
.policy("default"));
// result.safeText: "Contact {{person:p_001:2f9a4d81c6e0}} at {{email:e_001:b57e03d2a941}}"
String llmReply = callYourLLM(result.safeText); // sees tokens only
RehydrateResponse restored = client.rehydrate(new RehydrateRequest()
.output(llmReply)
.sessionState(result.sessionState)
.outputChannel("customer_email")
.restoreMode("formatted"));
// restored.restoredText: "Dear Frau Julia Schneider, ..."Guarded Call and Chat
guardedCall wraps any String -> String step: it transforms the input, runs your function on the tokenized text, and rehydrates the output. It fails closed, so if the transform returns no session state the function is never run and no unrestored output is returned.
String answer = client.guardedCall(
safe -> callYourLLM(safe),
"Draft a reply to Julia Schneider",
new OGuardAIClient.GuardedCallOptions().policy("default").outputChannel("customer_email"));guardedChat tokenizes a whole conversation in one call and returns the safe messages plus the session state needed to restore the reply.
List<Map<String, Object>> messages = List.of(
Map.of("role", "system", "content", "You are a support agent."),
Map.of("role", "user", "content", "I am Julia Schneider, julia@example.com"));
GuardedChatResult chat = client.guardedChat(messages,
new OGuardAIClient.GuardedChatOptions().policy("german-support"));
String reply = callYourChatLLM(chat.safeMessages); // sees tokens only
String restored = client.rehydrateReply(reply, chat.sessionState, "customer_email");Detect, Health, Capabilities
DetectResponse detected = client.detect(new DetectRequest("Email test@example.com, SSN 123-45-6789"));
for (DetectedEntity e : detected.entities) {
System.out.printf("%s: %s (%.2f)%n", e.type, e.value, e.confidence);
}
// includeValues is a boxed Boolean, so a false is transmitted verbatim to keep raw
// values out of a value-free caller (a primitive would be dropped and default to true).
client.detect(new DetectRequest("SSN 123-45-6789").includeValues(false));
HealthResponse health = client.health(); // health.status == "healthy"
CapabilitiesResponse caps = client.capabilities(); // caps.entityTypes, caps.languages, caps.detectorsSessions
A Session carries session state across a multi-turn exchange. Use one per conversation; it is not safe for concurrent use.
Session session = client.newSession("german-support");
TransformResponse turn1 = session.transform("Ich bin Anna Mueller, Kundennummer 948221");
String reply = callYourLLM(turn1.safeText);
RehydrateResponse restored = session.rehydrate(reply, "customer_email");
// Turn 2: entities are deduplicated across turns, so Anna Mueller stays p_001.
TransformResponse turn2 = session.transform("Meine E-Mail ist anna@example.com");Streaming
Streaming runs the full pipeline then streams the safe (transform) or restored (rehydrate) text to your consumer. The returned terminal event carries the session state needed to rehydrate a streamed transform. A terminal server error throws an OGuardAIException.
StringBuilder safe = new StringBuilder();
StreamEvent terminal = client.transformStream(
"Contact Julia Schneider",
new OGuardAIClient.TransformStreamOptions().policy("default"),
chunk -> safe.append(chunk));
String sessionState = terminal.sessionState; // rehydrate the streamed output with thisBatch
Transform or detect many inputs in one request. Each item may carry its own policy and language, so one batch can mix languages. sessionMode is independent (default) or shared.
BatchTransformResponse batch = client.batchTransform(new BatchTransformRequest()
.sessionMode("independent")
.items(List.of(
new BatchTransformRequest.Item("Ich bin Anna Mueller").language("de"),
new BatchTransformRequest.Item("Contact john@example.com").language("en"))));
for (BatchTransformResponse.Result r : batch.results) {
System.out.println(r.index + " " + r.safeText + " " + r.error);
}
// includeValues(false) keeps raw values out of a value-free caller.
BatchDetectResponse detected = client.batchDetect(new BatchDetectRequest()
.includeValues(false)
.items(List.of(new BatchDetectRequest.Item("SSN 123-45-6789").policy("strict-pii"))));Revocation and Policy Preview
// Revoke a value so future rehydrates return [DELETED].
client.revoke("email", "julia@example.com");
client.bulkRevoke(List.of(new RevokeRequest("email", "a@b.com")));
// Preview policy decisions without transforming (evaluate-policy).
EvaluatePolicyResponse decision = client.evaluatePolicy(
List.of(new PolicyEntity("email", "julia@example.com")),
"default");RAG
The RAG methods carry a corpusId for cross-document token identity: passing the same corpusId on ingest, query, and context makes the same raw value tokenize to the same token id across documents, which is what lets a query entity align with an ingested document entity.
// 1. Ingest: chunk + tokenize a document, store safe chunks in your vector DB
RagIngestResponse ingest = client.ragIngest(new RagIngestRequest(documentText)
.chunkingStrategy("paragraph")
.corpusId("support-kb"));
List<String> docSessions = ingest.chunks.stream()
.map(c -> c.chunkSessionState).collect(Collectors.toList());
// 2. Query: tokenize the user question for vector search
RagQueryResponse query = client.ragQuery(new RagQueryRequest(userQuestion).corpusId("support-kb"));
List<Hit> hits = vectorSearch(query.safeQuery);
// 3. Context: merge retrieved chunks into a safe LLM context
RagContextResponse ctx = client.ragContext(new RagContextRequest(
hits.stream().map(h -> h.text).collect(Collectors.toList()), query.sessionState)
.documentSessions(docSessions)
.corpusId("support-kb"));
String llmAnswer = callYourLLM(String.join("\n", ctx.safeChunks));
// 4. Answer: restore host-side using the accumulated RAG session
RagAnswerResponse answer = client.ragAnswer(new RagAnswerRequest(llmAnswer, ctx.sessionState)
.outputChannel("user_output"));
// GDPR erasure: forget every value in an ingest session
client.ragDelete(ingest.chunks.get(0).chunkSessionState);Error Handling
Every failure is an OGuardAIException (unchecked). Read code() for the specific failure, or use the predicate methods.
try {
client.transform(new TransformRequest().input("..."));
} catch (OGuardAIException e) {
if (e.isPolicyDenied()) {
// 403: a redacted entity's rule set on_redact: reject
} else if (e.isSessionExpired()) {
// 410: session TTL elapsed
} else if (e.isOutputBlocked()) {
// 422: the output guard blocked newly generated PII
} else if (e.isRateLimited()) {
String retryAfter = e.retryAfter();
} else {
System.out.printf("error [%s] status=%d%n", e.code(), e.statusCode());
}
}Predicates: isAuth, isValidation, isPolicyDenied, isSessionExpired, isOutputBlocked, isTokenRepair, isRateLimited, isTimeout.
Configuration
OGuardAIClient client = OGuardAIClient.builder()
.baseUrl("http://localhost:3000") // required
.apiKey("your-api-key") // optional: sent as X-API-Key
.timeout(Duration.ofSeconds(30)) // optional per-attempt timeout (default 30s)
.maxRetries(3) // optional; retries 429/503, honors Retry-After, 0 to disable
.build();Scope
This release covers transform, rehydrate, file rehydrate (rehydrateFile, the JSON /v1/rehydrate/file route), detect, health, capabilities, evaluate-policy, batch transform and detect, revoke and bulk revoke, revocation count, the full RAG pipeline (ingest, ingest-batch, query, context, answer, delete), session status and delete, the guarded-call, guarded-chat, and rehydrate-reply helpers, a stateful session, and streaming. The multipart transform file upload, the image endpoints, and the remaining admin endpoints (diagnostics, metrics, policy validate and reload) are served by the HTTP API and the TypeScript and Python SDKs; they are not yet wrapped in the Java client.