SDKs
Java and Spring Boot Integration
Call your Verdictan gateway from Java with the JDK's built-in HttpClient and Jackson. You build a small service class, run it once from Maven, and then register the same class as a Spring bean.
Before you start
- A running gateway from the quickstart, with its application token and a configured model.
- Java 17 or newer, and Maven.
Configure the client
Set three variables where the service runs:
export VERDICTAN_GATEWAY_URL="http://127.0.0.1:41002/v1"
export VERDICTAN_CLIENT_TOKEN="<application-token>"
export VERDICTAN_MODEL="<configured-model>"If the gateway runs on another host, use its base URL instead, still ending in /v1. The provider key stays on the gateway, and the application token stays on your server.
For a standalone try-out, save this pom.xml in a new directory. In an existing project, use its own Jackson version instead of adding a second one.
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>verdictan-example</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.20.1</version>
</dependency>
</dependencies>
<build><plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.6.3</version>
</plugin>
</plugins></build>
</project>Send a request
Save this class in src/main/java:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.Map;
public final class GovernedChatService {
private final HttpClient http = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
private final ObjectMapper json;
private final URI completionsUri;
private final String token;
private final String model;
public GovernedChatService(
ObjectMapper json,
String gatewayUrl,
String token,
String model) {
this.json = json;
this.completionsUri = URI.create(
gatewayUrl.replaceAll("/+$", "") + "/chat/completions");
this.token = token;
this.model = model;
}
public static void main(String[] args) throws Exception {
var service = new GovernedChatService(new ObjectMapper(),
required("VERDICTAN_GATEWAY_URL"), required("VERDICTAN_CLIENT_TOKEN"),
required("VERDICTAN_MODEL"));
System.out.println(service.complete("Reply with Hello."));
}
private static String required(String name) {
var value = System.getenv(name);
if (value == null || value.isBlank()) throw new IllegalStateException("Set " + name);
return value;
}
public String complete(String input) throws Exception {
var payload = Map.of(
"model", model,
"messages", List.of(Map.of("role", "user", "content", input))
);
var request = HttpRequest.newBuilder(completionsUri)
.timeout(Duration.ofSeconds(60))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json.writeValueAsString(payload)))
.build();
var response = http.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Request ID: " +
response.headers().firstValue("x-request-id").orElse("unavailable"));
if (response.headers().firstValue("x-verdictan-verdict")
.filter("ESCALATE"::equals).isPresent()) {
return "Waiting for human review. Do not resend yet.";
}
JsonNode body = null;
try {
body = json.readTree(response.body());
} catch (JsonProcessingException ignored) {
// Do not expose a provider error body that is not JSON.
}
if (response.statusCode() == 400
&& body != null
&& "content_policy_violation".equals(
body.path("error").path("code").asText())) {
throw new IllegalStateException("Request blocked by policy");
}
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new IllegalStateException(
"Gateway returned HTTP " + response.statusCode());
}
if (body == null) {
throw new IllegalStateException("Gateway returned invalid JSON");
}
JsonNode content = body.path("choices").path(0).path("message").path("content");
if (!content.isTextual() || content.asText().isBlank()) {
throw new IllegalStateException("Gateway returned invalid content");
}
return content.asText();
}
}Run it from the directory with pom.xml:
mvn compile exec:java -Dexec.mainClass=GovernedChatServiceYou see a request ID and a short answer. The class makes one attempt, with a 5-second connect timeout and a 60-second request timeout.
Use it in Spring
- Register
GovernedChatServiceas a bean. Pass your application'sObjectMapperand the three values above, for example from@Valueproperties. - Inject the bean where your code needs a model call, and call
complete(input). - Call it from a thread that can block. In a reactive application, use an asynchronous HTTP client or run the call on a scheduler for blocking work.
The example uses Jackson 2 (com.fasterxml.jackson). On Spring Boot 4 with Jackson 3, change the imports to the Jackson 3 packages.
Handle gateway decisions
Every response carries x-verdictan-verdict and x-verdictan-reason-code headers. The example already handles escalations and blocks. Handle the rest in your service:
| Response | What it means | What to do |
|---|---|---|
200 with x-verdictan-verdict: ESCALATE | A policy sent the request for human review. Content is null and finish_reason is content_filter. | Tell the user it is under review. Don't resend. |
400 with code content_policy_violation | A policy blocked the request. | Show a refusal. Other 400 codes are malformed requests. |
401 or 403 | The token is missing, revoked, or not allowed. | Check the application token and its access. |
402 | A budget or balance limit. | Check the error code. |
429 | A rate limit. | Honor Retry-After if present, within your retry limit. |
Timeout or 5xx | The gateway or provider failed. | Investigate before a retry: the provider may have already processed the request. |
A 200 without text can also be a tool call, so don't treat every empty answer as an escalation. See Error Handling for the full list.
Find the request in Verdictan
Open History in your organization and search for the printed request ID. Request content is visible only when capture is on in the assigned configuration. Without capture, find the decision in the delivered gateway events.
Next steps
- Debugging Requests: trace a request that behaves unexpectedly.
- API Key Management: create and rotate application tokens.
- Runtime Request Families: the other APIs the gateway serves.