Java and Spring Boot Integration
Use Java's HTTP client against the gateway's documented Chat Completions route. A Spring Boot service can register the same class as a bean without depending on a version-specific AI framework adapter.
Prerequisites
- Complete the Quickstart and keep the gateway running.
- Create a client API token for the application.
- Use Java 17 or later. Spring Boot already includes Jackson; a plain Java application must add Jackson Databind if it uses the example below.
Set application configuration from secrets or environment variables:
export KEEPTRUSTS_GATEWAY_URL="http://127.0.0.1:41002/v1"
export KEEPTRUSTS_REQUEST_TOKEN="kt_..."
export KEEPTRUSTS_MODEL="gpt-5.4-mini"
Create a governed client
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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 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());
JsonNode body = json.readTree(response.body());
if (response.statusCode() == 400
&& "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 " + response.statusCode() + ": " + response.body());
}
return body.path("choices").path(0).path("message").path("content").asText();
}
}
Construct this service from Spring configuration and register it as a bean:
import org.springframework.context.annotation.Bean;
@Bean
GovernedChatService governedChatService(ObjectMapper json) {
return new GovernedChatService(
json,
requireEnv("KEEPTRUSTS_GATEWAY_URL"),
requireEnv("KEEPTRUSTS_REQUEST_TOKEN"),
requireEnv("KEEPTRUSTS_MODEL")
);
}
private static String requireEnv(String name) {
String value = System.getenv(name);
if (value == null || value.isBlank()) {
throw new IllegalStateException(name + " is required");
}
return value;
}
The application token is not the upstream provider credential, and the model name must match a configured target returned by the gateway.
Treat health and authorization separately
GET /healthzchecks gateway process health.- Authenticated
GET /v1/modelsverifies the client token and available model catalog.
Use the second check for application readiness because it exercises the same authorization boundary as model requests.
Retry carefully
Do not retry a 400 content_policy_violation, 401, or 403. A 429 or 5xx can be transient, but a
timeout can occur after an upstream provider accepted the request. Add retries
only when the application has a deliberate duplicate-work and duplicate-cost
policy.
Verify the governed path
curl -fsS \
-H "Authorization: Bearer ${KEEPTRUSTS_REQUEST_TOKEN}" \
"${KEEPTRUSTS_GATEWAY_URL}/models"
kt events tail --since 10m --follow
Send a representative Java request while the event tail is active. The matching event is the proof that traffic traversed Keeptrusts.