Java
Pure-JVM AI agent detection for the JDK HttpServer and Servlet containers
Overview
Checkpoint for Java embeds the same kya-os-engine WASI build the .NET SDK and the DNS gateway run, and executes it through Chicory, a pure-Java WebAssembly runtime. There are no native dependencies, and it requires JDK 21 or later.
Every request is classified by the embedded engine, producing identical verdicts to the DNS gateway and the .NET and TypeScript SDKs (asserted by the java-wasi row of pnpm cross-runtime-parity), and detections report to the dashboard's /api/v1/log-detection with sdk.runtime: "jvm".
Prerequisites
- A Checkpoint project: get your Project ID and API key from Installations in the dashboard.
- JDK 21 or later.
- A
com.sun.net.httpserverbased app, or ajakarta.servletcontainer.
Installation
Not yet on a public registry
The Java SDK is not published to Maven Central or any other public registry today. Build it from
source with mvn -f packages/checkpoint-java/pom.xml verify, or vendor the shaded
checkpoint-jdk-httpserver jar produced by that build into your own artifact repository.
The Maven coordinates, once you have a jar built or vendored:
<dependency>
<groupId>ai.knowthat.checkpoint</groupId>
<artifactId>checkpoint-core</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>ai.knowthat.checkpoint</groupId>
<artifactId>checkpoint-jdk-httpserver</artifactId>
<version>0.2.0</version>
</dependency>Swap checkpoint-jdk-httpserver for checkpoint-servlet if you run inside a servlet container. An app that already carries a different Jackson version should use the *-shaded.jar variant (everything relocated under ai.knowthat.checkpoint.shaded.*) or pin Jackson through its own dependency management, since the thin jars resolve Chicory, ASM, and Jackson onto the host classpath.
Quick Setup
Checkpoint checkpoint = Checkpoint.create(CheckpointOptions.builder()
.projectId("<project-id>")
.apiKey("sk_live_...")
// The builder default is OBSERVE, which never blocks. Set ENFORCE to
// actually short-circuit.
.enforcementMode(CheckpointOptions.EnforcementMode.ENFORCE)
.onAgentDetected(EnforcementAction.BLOCK)
.build());
// jdk.httpserver:
context.getFilters().add(CheckpointFilter.wrap(checkpoint));
// servlet containers:
new CheckpointServletFilter(checkpoint);Call checkpoint.warmup() during your app's own startup to pre-warm the engine pool off the request path; both adapters already call it synchronously during their own init, so size your container's health-check timeout accordingly.
Enforce vs Observe Mode
enforcementMode defaults to OBSERVE, which never short-circuits a request: every response the engine decided carries KYA-Mode: observe, and whatever onAgentDetected (or the dashboard policy) would have done, the request passes through with KYA-Would-Have-Been: <block|redirect|instruct|challenge> and KYA-Would-Have-Been-Reason: <threshold|deny_list|configured_action|...>. The telemetry event reports enforcement.action: "log" with the withheld action in enforcement.wouldBlock. Set ENFORCE to actually block, redirect, instruct, or challenge.
onAgentDetected defaults to EnforcementAction.LOG and confidenceThreshold defaults to 70.
Client IP: Trust Profiles
The client IP the engine sees is the direct TCP peer by default, which is the load balancer's own address once anything sits in front of the app: the engine's Tier-2 vendor-IP cross-match then never fires against the real caller. Both adapters ship the same Direct/Vercel/Cloudflare/TrustedProxy header-precedence profiles the TypeScript and .NET SDKs have:
checkpoint-servlet:CheckpointServletFilter's two-argument constructor takes aClientIpTrustPolicy, or configure it via theclientIpPolicy(vercel/cloudflare/trusted-proxy) andclientIpTrustedHops(hop count fortrusted-proxy) filter init-params. Default staysdirectPeer().checkpoint-jdk-httpserver:RemoteIpResolver.vercel(),.cloudflare(), and.trustedProxy(int hops)factories, passed toCheckpointFilter.create/.wrapthe same waydirectPeer()always was. A fifth, Checkpoint-specific profile,RemoteIpResolver.gateway(), trusts the gateway'sKYA-Client-IPheader for an origin deployed directly behind the Checkpoint Cloudflare gateway; it additionally verifies the direct peer against Cloudflare's published edge IPv4 ranges before trusting the header at all.
Every profile is opt-in, and none of them is safe to select on an origin any untrusted caller can also reach directly. Either adapter also accepts a fully custom RemoteIpResolver / ClientIpResolver for a topology none of the built-in profiles cover.
Response Headers
Every response includes detection metadata:
If a burst of traffic exhausts the bounded WASM engine pool (maxEngineInstances, engineAcquireTimeout), the request degrades to detection-unavailable rather than blocking on a cold instantiation: KYA-Detection: unavailable is stamped, and under failOpen (the default) the request still passes through with KYA-Failopen: true.
Signature and Delegation Verification
ChatGPT's RFC 9421 HTTP Message Signatures are verified whenever a request carries Signature / Signature-Input headers: the SDK reconstructs the signature base and verifies it with the JDK's native Ed25519 support against a live-fetched public key, no configuration required. A verified signature yields 100% confidence.
Separately, a request that presents a delegation credential (Authorization: Bearer <VC-JWT>, or the KYA-Delegation marker header for hosts that strip Authorization) with no kya-http/2 signature routes to the engine's standalone delegation-verify entry instead of plain detection. Unlike the .NET SDK's live did:web resolution, the Java SDK resolves trusted delegation roots, DID documents, and revocation lists from static, operator-configured maps on CheckpointOptions rather than fetching them live.
Dashboard Reporting
Detection events are buffered and flushed as a batch to POST /api/v1/log-detection, fire-and-forget after the request completes; pure human traffic is not reported. Requires projectId and apiKey to be set: without them, detection and enforcement still run, only the dashboard hop is skipped. Raw IP and full URLs are reported by default; .telemetryHashIp(true) on the builder salts the IP with a project-scoped SHA-256 hash before it leaves the process, and .telemetryStripQuery(true) strips the ?query suffix from reported paths and URLs.
Coming in the Next Release
The following are in progress on open branches and not yet part of a released Java SDK version; they are described here so this page does not need a rewrite the moment they ship, but nothing below should be treated as current behavior:
- A build-time check that
projectIdis the dashboard project UUID (not its friendly id) wheneverbrowserPostureis configured, since the posture cookie name and its signed claim are both the UUID. - Additional
/log-detectiontelemetry fields: the resolved enforcement mode, the deciding composed-policy id, a per-stage engine trace, request latency, the verified browser-posture claim, and a reported-identity block. - Additional
EngineConfigoptions onCheckpointOptionscovering tenant-policy handling for verified-vendor traffic, a Noisy-OR probability combiner (shadow or live), a session-velocity escalation hint, a scanner-misclassification hint, and trusting a fixed set of Cloudflare-supplied transport-signal headers when this deployment sits behind Cloudflare exclusively. - A bounded seen-nonce cache for kya-http replay detection.
Build
mvn -f packages/checkpoint-java/pom.xml verifyThe embedded kya-os-engine.wasm is sha256-pinned by a drift test; re-baselining is documented alongside that test. Versioning follows the .NET precedent: one version for all modules, set in the parent pom.xml. Block responses currently use the .NET SDK's JSON response mode; the plain-text agent-notice mode and its KYA-Block-Reason header parity with .NET are not ported yet.
