Build OpenCode Zen requests in Java with consistent headers, credentials, session identifiers, and tool declarations. The starter provides an auto-configured request factory; your application sends requests with its own HTTP client and handles the responses.
- Three inference formats — request construction for Chat Completions, Responses, and Anthropic Messages.
- Spring Boot integration — inject
OpenCodeZenRequestFactory, or provide your own bean to override the default. - Request compatibility — a bundled OpenCode 2.1.4 client identity, the official 2.x system prompt and the complete official tool set (the application's tools leave under
clint_names), a fresh session identifier per request (sticky per-conversation sessions behindopencode.zen.session-affinity), and a per-request project identifier. - Model and credential policies — catalog parsing, free-model admission rules, and public versus API-key authentication.
- Streaming utilities — SSE frame decoding, JSON event reading, and upstream error classification.
- Explicit transport choices — helpers for direct, JVM-configured, or HTTP proxy connections. No automatic route switching; retrying is opt-out through
OpenCodeZenRetryPolicy(a rate-limited 429 asks again under a fresh ledger; nothing else is retried).
This project supplies request and protocol utilities. Credential storage, routing, budgets, tool execution, and application workflows belong to the consuming application.
- Java 21 or later.
- Spring Boot dependencies are built against 3.3.2; other versions should be verified in your application.
- Artifacts are distributed through JitPack. The documented version is
v1.5.5, including the leadingv.
Gradle Kotlin DSL
repositories {
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
dependencies {
implementation("com.github.BangShou1st.opencode-zen:opencode-zen-spring-boot-starter:v1.5.5")
}If your build centralizes repositories, add JitPack to dependencyResolutionManagement.repositories in settings.gradle.kts.
Maven
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.github.BangShou1st.opencode-zen</groupId>
<artifactId>opencode-zen-spring-boot-starter</artifactId>
<version>v1.5.5</version>
</dependency>
</dependencies>Public artifact downloads require no token. JitPack may need time to build a tag on its first resolution; check the build page if a dependency is unavailable.
The starter registers the request factory automatically. No library configuration is required.
import com.sharedlibs.opencodezen.autoconfigure.OpenCodeZenHttpClients;
import com.sharedlibs.opencodezen.autoconfigure.OpenCodeZenRequestFactory;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.net.http.HttpClient;
import java.net.http.HttpResponse;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@Service
public class ZenChatService {
private final OpenCodeZenRequestFactory factory;
private final HttpClient http = OpenCodeZenHttpClients.system();
public ZenChatService(OpenCodeZenRequestFactory factory) {
this.factory = factory;
}
public HttpResponse<String> chat(UUID conversationId, String model, String prompt)
throws IOException, InterruptedException {
var payload = Map.<String, Object>of(
"model", model,
"messages", List.of(Map.of("role", "user", "content", prompt)));
// true/null selects public credentials for an eligible free model.
// For a paid model, pass false and your API key instead.
var request = factory.newConversationChatRequest(
true, null, conversationId, payload);
return http.send(request, HttpResponse.BodyHandlers.ofString());
}
}Pass an eligible free-model ID from the current catalog and admission policy. Reuse the same conversationId across turns. The example returns the upstream status and raw body; the application must check the status and handle JSON or SSE before exposing a result.
| Format | Protocol | Inference path |
|---|---|---|
| Chat Completions | ZenModelProtocol.CHAT |
/inference/openai/v1/chat/completions |
| Responses | ZenModelProtocol.RESPONSES |
/inference/openai/v1/responses |
| Anthropic Messages | ZenModelProtocol.ANTHROPIC |
/inference/anthropic/v1/messages?beta=true |
The 2.1 client groups its inference routes by provider family; OpenCodeZenProtocolPolicy.inferenceEndpoint(protocol) returns the grouped URL for the bundled base https://opencode.ai/inference/v1, and a custom base URL keeps the flat route. Use newConversationInferenceRequest(endpoint, protocol, freeModel, apiKey, conversationId, payload) for an explicit endpoint and format, or newEphemeralInferenceRequest(endpoint, protocol, freeModel, apiKey, payload) for a one-off call. The endpoint path must match the protocol.
For a streaming payload, request "stream": true and use an input-stream body handler:
import com.sharedlibs.opencodezen.OpenCodeZenSseEventReader;
// http and request are supplied by the application.
var response = http.send(request, HttpResponse.BodyHandlers.ofInputStream());
try (var body = response.body()) {
if (response.statusCode() != 200) {
throw new IOException("OpenCode Zen returned HTTP " + response.statusCode());
}
OpenCodeZenSseEventReader.read(body, event -> {
// Handle event.name(), event.payload(), and event.done().
});
}The reader handles SSE framing and JSON events. Your adapter must assemble protocol-specific output and filter tool calls. Tool calls in a reply name the clint_ wire spelling; restore the names your application declared through the renames that OpenCodeZenProtocolPolicy.shapeEnvelope returns. The appended official tools are declarations only and must never be executed. Only tools explicitly authorized by the caller may run.
- Free-model requests use the
publiccredential; paid models require an API key. The caller supplies the free/paid decision using the model policies. OpenCodeZenHttpClients.system()uses the JVM proxy selector. It does not guarantee support forHTTP_PROXY/HTTPS_PROXYenvironment variables.- Use
direct()for a direct connection orexplicitHttpProxy(host, port)for an explicit HTTP proxy. OpenCodeZenUpstreamDiagnostics.classify(statusCode, bodyBytes)distinguishes abuse rejection, free-tier rejection, rate limiting, and other failures. A 429 whose body carries the IP-abuse wording is a block, not a rate limit, and is never retried.OpenCodeZenRetryPolicycarries the retry decision and its knobs (aligned with upstream OpenCode-Zen-FreeX v0.5.10/v0.5.11): the tier ledgers its free quota per project id and egress together, so a rate-limited 429 is worth asking again under a fresh ledger, and so is a request that never got an answer (looksLikeTransportFailure). The count defaults to 2 (OPENCODE_ZEN_FREE_RETRY_429=0turns it off, 5 is the ceiling) and the first wait is 1200 ms, doubled per attempt, capped at 5 s (OPENCODE_ZEN_FREE_RETRY_WAIT_MSoverrides). The loop belongs to the caller: rebuild the request through the same factory method — every call mints fresh session, request and project identifiers, which is the fresh ledger — and hand the failure of the last attempt on unchanged when the retries are spent.opencode.zen.user-agent,opencode.zen.session-affinity(defaultfalse) andopencode.zen.relay-url(default blank) are the library's configuration properties. With affinity off, every request leaves as its own session; with affinity on, one conversation key keeps one sticky session for the factory's lifetime. The defaultautoneeds no configuration: an already-running local Bun relay wins, then the library starts the bundled relay script itself — with the local Bun, or, when none is installed, with a Bun runtime it downloads once (pinned release, SHA-256 verified, cached per user). From the second run on, everything is offline. Only an offline machine without Bun goes direct, which the tier refuses with429 FreeUsageLimitError;relay-urlalso accepts an explicit relay address ornone. Credentials remain application-owned.
- With
session-affinityenabled, conversation sessions are cached in memory within the factory instance; the mapping does not persist across restarts. - Inference overloads accepting
incomingHeadersselectively forward project, validated session, and Anthropic beta/version headers. They do not allow incoming headers to override credentials. - Legacy
newConversationRequest/newEphemeralRequestmethods return header-bearing builders only. For inference, applyOpenCodeZenProtocolPolicy.applyRequiredEnvelopeyourself or use the complete inference methods above.
| Module | Purpose |
|---|---|
opencode-zen-spring-boot-starter |
Dependency entry point for Spring Boot applications. |
opencode-zen-spring-boot-autoconfigure |
Protocol policies, model parsing, SSE utilities, properties, and request factory. |
The bundled client profile, official prompt and official tool set reference OpenCode-Zen-FreeX v0.5.9. Source attribution and its MIT notice are retained in THIRD_PARTY_NOTICES.md.
This is an independent Java integration, unaffiliated with the OpenCode project. Model availability, pricing, and access are controlled by the upstream service; using this package does not grant service access.
MIT © 2026 BangShou1st. Third-party components retain their respective licenses.