clawdforge/clients/java
Kayos 0d3ee26e24 clients/java: initial Java SDK for clawdforge
- Java 17, Maven, JDK java.net.http.HttpClient, Jackson 2.x
- ForgeClient (builder), records for RunResult / FileToken / AppToken / HealthStatus
- ApiException / AuthException / TransportException all extend ForgeException
  (RuntimeException) — checked exceptions feel un-modern in Java 17
- Multipart upload streams from disk via BodyPublishers.ofByteArrays
- 14 JUnit 5 tests against in-process com.sun.net.httpserver — zero test deps
  beyond JUnit
- mvn package / mvn test / mvn javadoc:javadoc clean
- snake_case wire format mapped to camelCase Java accessors via @JsonProperty
2026-04-28 22:49:06 -07:00
..
examples clients/java: initial Java SDK for clawdforge 2026-04-28 22:49:06 -07:00
src clients/java: initial Java SDK for clawdforge 2026-04-28 22:49:06 -07:00
pom.xml clients/java: initial Java SDK for clawdforge 2026-04-28 22:49:06 -07:00
README.md clients/java: initial Java SDK for clawdforge 2026-04-28 22:49:06 -07:00

clawdforge-client (Java)

Java SDK for clawdforge — a LAN-only HTTP service that wraps claude -p subprocess calls behind a bearer-token-gated REST API.

  • Java 17+ (records, sealed-friendly, switch expressions)
  • HTTP: JDK built-in java.net.http.HttpClient — no Apache HttpClient, no OkHttp
  • JSON: Jackson (jackson-databind 2.x)
  • Build: Maven
  • License: MIT

Install

This SDK isn't published to Maven Central. Build from source and install to your local Maven repo:

git clone http://192.168.0.5:3001/Sulkta-Coop/clawdforge.git
cd clawdforge/clients/java
mvn install

Then depend on it:

<dependency>
    <groupId>com.clawdforge</groupId>
    <artifactId>clawdforge-client</artifactId>
    <version>0.1.0</version>
</dependency>

Quickstart

import com.clawdforge.ForgeClient;
import com.clawdforge.RunRequest;
import com.clawdforge.RunResult;
import com.fasterxml.jackson.databind.JsonNode;

ForgeClient client = ForgeClient.builder()
    .baseUrl("http://192.168.0.5:8800")
    .token(System.getenv("CLAWDFORGE_TOKEN"))
    .build();

RunResult res = client.run(RunRequest.builder()
    .prompt("Reply with JSON: {\"hello\": \"world\"}")
    .model("sonnet")
    .timeoutSecs(60)
    .build());

System.out.println(res.durationMs() + "ms");

JsonNode r = res.result();
if (r.isObject()) {
    System.out.println(r.get("hello").asText());
}

Naming & wire format

The clawdforge HTTP API uses snake_case on the wire (timeout_secs, file_token, created_at, ...). This SDK uses idiomatic Java camelCase everywhere; Jackson @JsonProperty annotations on each record handle the mapping. Examples:

Java accessor Wire field
RunRequest#timeoutSecs timeout_secs
RunResult#durationMs duration_ms
RunResult#stopReason stop_reason
FileToken#fileToken file_token
FileToken#ttlSecs ttl_secs
HealthStatus#claudePresent claude_present
HealthStatus#claudeVersion claude_version
AppToken#ipCidrs ip_cidrs
AppToken#createdAt created_at

Public surface

ForgeClient

Build via ForgeClient.builder():

ForgeClient client = ForgeClient.builder()
    .baseUrl("http://192.168.0.5:8800")  // trailing slash trimmed
    .token("cf_...")                     // required bearer token
    .defaultTimeout(Duration.ofSeconds(120))  // optional, default 120s
    .httpClient(myCustomHttpClient)      // optional, default is JDK default
    .build();

Methods (all may throw subclasses of ForgeException):

Method Endpoint Returns
healthz() GET /healthz HealthStatus
run(RunRequest) POST /run RunResult
uploadFile(Path, int ttlSecs) POST /files FileToken
createToken(String name, List<String> ipCidrs) POST /admin/tokens AppToken
listTokens() GET /admin/tokens List<AppToken>
revokeToken(String name) DELETE /admin/tokens/<name> void

RunRequest

Build via RunRequest.builder():

RunRequest req = RunRequest.builder()
    .prompt("...")               // required
    .model("sonnet")             // optional, server default = "sonnet"
    .system("You are ...")       // optional system prompt
    .files(List.of("ff_..."))    // optional file tokens from uploadFile()
    .timeoutSecs(60)             // optional, server clamps to [5, 600]
    .build();

Records

Public state is exposed via Java 17 records:

  • HealthStatus(boolean ok, boolean claudePresent, String claudeVersion)
  • RunResult(boolean ok, JsonNode result, long durationMs, String stopReason)
  • FileToken(String fileToken, int ttlSecs, long size)
  • AppToken(String name, String token, List<String> ipCidrs, long createdAt)

RunResult.result() is a Jackson JsonNode because the server may return either a structured JSON value (when the prompt asked for JSON) or a plain JSON string. Narrow with isObject() / isTextual() / etc.

Error model — why unchecked?

All SDK errors extend ForgeException, which extends RuntimeException. Checked exceptions feel un-modern in Java 17 — they bleed across API boundaries, force throws chains in lambdas, and don't pair well with the records / sealed-interface idioms the rest of this SDK leans on.

ForgeException                    (RuntimeException)
├── ApiException                  (any non-2xx HTTP)
│   └── AuthException             (401 / 403)
└── TransportException            (DNS, connect, IO, decode, timeout)

Catch order:

try {
    client.run(req);
} catch (AuthException e) {
    // token revoked, IP not allowed
} catch (ApiException e) {
    // 502 from /run failure, 404 from missing token, etc.
    // e.statusCode() and e.body() are both available
} catch (TransportException e) {
    // network / IO / JSON-decode failures
} catch (ForgeException e) {
    // catch-all for anything else SDK-level
}

File uploads

uploadFile streams the file from disk in 1 MiB chunks via HttpRequest.BodyPublishers.ofByteArrays rather than slurping the whole file into memory:

FileToken ft = client.uploadFile(Path.of("./recipe.png"), 3600);
client.run(RunRequest.builder()
    .prompt("Extract recipe data from the attached image.")
    .files(List.of(ft.fileToken()))
    .build());

ttlSecs is clamped server-side to [60, 86400]. Pass 0 to use the server default of 3600.

Build

mvn package        # produces target/clawdforge-client-0.1.0.jar
mvn test           # runs the JUnit 5 suite
mvn javadoc:javadoc

The test suite uses the JDK built-in com.sun.net.httpserver.HttpServer in-process — no external mock-server dependency.

Threading

ForgeClient instances are immutable and safe to share across threads. Construct once, reuse everywhere.