Java SDK for interacting with Claude Code CLI. This is a pure Java implementation that mirrors the design of the official Python and TypeScript Claude Agent SDKs.
| Feature | Description |
|---|---|
| Simple One-Shot API | Query.text() for quick answers in one line |
| Blocking Client | ClaudeSyncClient for multi-turn conversations with Iterator |
| Reactive Client | ClaudeAsyncClient with Flux/Mono for Spring WebFlux |
| Hook System | Register callbacks for tool use events |
| MCP Integration | Support for Model Context Protocol servers |
| Permission Callbacks | Programmatic control over tool execution |
- Tutorial β Step-by-step guide covering all SDK features
- Tutorial Source Code β Runnable examples for each tutorial module
The tutorial covers:
- All three API styles (Query, ClaudeSyncClient, ClaudeAsyncClient)
- Multi-turn conversations and session management
- Hooks, permission callbacks, and MCP integration
- Real-world patterns and best practices
Each module is a standalone runnable example with integration tests.
- Java 17+
- Claude Code CLI installed and authenticated
- Maven 3.8+
Available on Maven Central β view on Maven Central
<dependency>
<groupId>io.github.markpollack</groupId>
<artifactId>claude-code-sdk</artifactId>
<version>1.0.0</version>
</dependency>dependencies {
implementation 'io.github.markpollack:claude-code-sdk:1.0.0'
}git clone https://github.com/markpollack/claude-agent-sdk-java.git
cd claude-agent-sdk-java
./mvnw install| API | Class | Programming Style | Best For |
|---|---|---|---|
| One-shot | Query |
Static methods | Simple scripts, CLI tools |
| Blocking | ClaudeSyncClient |
Iterator-based | Traditional applications, synchronous workflows |
| Reactive | ClaudeAsyncClient |
Flux/Mono | Non-blocking applications, high concurrency |
Both ClaudeSyncClient and ClaudeAsyncClient support the full feature set: multi-turn conversations, hooks, MCP integration, and permission callbacks. They differ only in programming paradigm (blocking vs non-blocking).
Factory Pattern: Use ClaudeClient.sync() or ClaudeClient.async() to create clients.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β YOUR APPLICATION β
βββββββββββββββββ¬ββββββββββββββββββββββ¬ββββββββββββββββββ¬ββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββββ βββββββββββββββββββββ βββββββββββββββββββ
β Query β β ClaudeSyncClient β β ClaudeAsyncClientβ
β (one-shot) β β (blocking) β β (reactive) β
β β β β β β
β Query.text() β β Iterator-based β β Flux/Mono β
β Query.execute() β β Multi-turn β β Spring WebFluxβ
βββββββββββ¬ββββββββββ βββββββββββ¬ββββββββββ ββββββββββ¬βββββββββ
β β β
βββββββββββββββββββββββββΌβββββββββββββββββββββββ
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β StreamingTransport β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β’ Subprocess management (Process API) ββ
β β β’ JSON-LD streaming via stdin/stdout ββ
β β β’ State machine: DISCONNECTED β CONNECTED β CLOSED ββ
β β β’ Thread-safe with separate schedulers ββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Claude Code CLI β
β (claude --output-format stream-json) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββ βββββββββββββββββββ ββββββββββββ
β Your Code β β StreamingTransportβ β Claude β
ββββββββ¬ββββββββ ββββββββββ¬βββββββββ ββββββ¬ββββββ
β β β
β connect("Hello") β β
β βββββββββββββββββββββββββ>β spawn process β
β β ββββββββββββββββββββββ>β
β β β
β β SystemMessage β
β β<ββββββββββββββββββββββββ
β Iterator/Flux yields β β
β<βββββββββββββββββββββββββββ AssistantMessage β
β β<ββββββββββββββββββββββββ
β process message... β β
β<βββββββββββββββββββββββββββ ResultMessage β
β β<ββββββββββββββββββββββββ
β (turn complete) β β
β β β
β query("Follow-up") β β
β βββββββββββββββββββββββββ>β write to stdin β
β β ββββββββββββββββββββββ>β
β β β
β Iterator/Flux yields β AssistantMessage β
β<βββββββββββββββββββββββββββ<ββββββββββββββββββββββββ
β β β
β close() β terminate process β
β βββββββββββββββββββββββββ>β ββββββββββββββββββββββ>β
β β β
βΌ βΌ βΌ
The simplest way to use Claude - one line of code:
import io.github.markpollack.claude.agent.sdk.Query;
String answer = Query.text("What is 2+2?");
System.out.println(answer); // "4"String answer = Query.text("Explain quantum computing",
QueryOptions.builder()
.model("claude-sonnet-4-20250514")
.appendSystemPrompt("Be concise")
.timeout(Duration.ofMinutes(5))
.build());QueryResult result = Query.execute("Write a haiku about Java");
result.text().ifPresent(System.out::println);
System.out.println("Cost: $" + result.metadata().cost().calculateTotal());
System.out.println("Duration: " + result.metadata().getDuration().toMillis() + "ms");For multi-turn conversations, hooks, and MCP servers:
import io.github.markpollack.claude.agent.sdk.ClaudeClient;
import io.github.markpollack.claude.agent.sdk.ClaudeSyncClient;
try (ClaudeSyncClient client = ClaudeClient.sync()
.workingDirectory(Path.of("."))
.model("claude-sonnet-4-20250514")
.build()) {
// Simplest: just get the text (80% use case)
String answer = client.connectText("What is 2+2?");
System.out.println(answer); // "4"
// Follow-up with context preserved
String followUp = client.queryText("Multiply that by 10");
System.out.println(followUp); // "40"
}When you need message metadata, tool use details, or cost information:
try (ClaudeSyncClient client = ClaudeClient.sync()
.workingDirectory(Path.of("."))
.build()) {
// For-each with good toString() on all message types
for (Message msg : client.connectAndReceive("List files in current directory")) {
System.out.println(msg); // AssistantMessage, ResultMessage, etc.
}
}HookRegistry hookRegistry = new HookRegistry();
// Block dangerous commands
hookRegistry.registerPreToolUse("Bash", input -> {
if (input instanceof HookInput.PreToolUseInput preToolUse) {
String cmd = preToolUse.getArgument("command", String.class).orElse("");
if (cmd.contains("rm -rf")) {
return HookOutput.block("Dangerous command blocked");
}
}
return HookOutput.allow();
});
try (ClaudeSyncClient client = ClaudeClient.sync()
.workingDirectory(Path.of("."))
.permissionMode(PermissionMode.DEFAULT)
.hookRegistry(hookRegistry)
.build()) {
// Hooks intercept tool calls
}For reactive applications using Project Reactor:
ClaudeAsyncClient client = ClaudeClient.async()
.workingDirectory(Path.of("."))
.model("claude-sonnet-4-20250514")
.permissionMode(PermissionMode.BYPASS_PERMISSIONS)
.build();
// Stream text as it arrives
client.connect("Explain recursion").textStream()
.doOnNext(System.out::print)
.subscribe();client.connect("My favorite color is blue.").text()
.doOnSuccess(System.out::println)
.flatMap(r1 -> client.query("What is my favorite color?").text())
.doOnSuccess(System.out::println) // Claude remembers: "blue"
.flatMap(r2 -> client.query("Spell it backwards").text())
.doOnSuccess(System.out::println) // "eulb"
.subscribe();When you need all message types (tool use, metadata, etc.):
client.query("List files").messages()
.doOnNext(System.out::println) // Good toString() on all types
.subscribe();// Via ClaudeClient builder
ClaudeSyncClient client = ClaudeClient.sync()
.workingDirectory(Path.of("."))
.model("claude-sonnet-4-20250514")
.systemPrompt("You are a helpful assistant")
.permissionMode(PermissionMode.DEFAULT)
.timeout(Duration.ofMinutes(5))
.hookRegistry(hookRegistry)
.build();
// Or via CLIOptions
CLIOptions options = CLIOptions.builder()
.model("claude-sonnet-4-20250514")
.permissionMode(PermissionMode.DEFAULT)
.systemPrompt("You are a helpful assistant")
.appendSystemPrompt("Be concise")
.maxTurns(10)
.allowedTools(List.of("Read", "Grep"))
.disallowedTools(List.of("Bash"))
.build();
ClaudeSyncClient client = ClaudeClient.sync(options)
.workingDirectory(Path.of("."))
.build();claude-agent-sdk-java/
βββ claude-code-sdk/ # Core SDK module
β βββ src/
β βββ main/java/io/github/markpollack/claude/agent/sdk/
β β βββ Query.java # Simple one-shot API
β β βββ ClaudeClient.java # Factory: sync() / async()
β β βββ ClaudeSyncClient.java # Blocking client interface
β β βββ ClaudeAsyncClient.java # Reactive client interface
β β βββ transport/ # StreamingTransport
β β βββ streaming/ # MessageStreamIterator
β β βββ hooks/ # HookRegistry, HookCallback
β β βββ permission/ # ToolPermissionCallback
β β βββ mcp/ # MCP server configuration
β β βββ types/ # Message types, content blocks
β β βββ parsing/ # JSON parsing, control messages
β βββ test/
βββ examples/
βββ hello-world/ # All three APIs demonstrated
βββ email-agent/ # ClaudeAsyncClient with Vaadin UI
βββ excel-demo/ # ClaudeAsyncClient streaming
βββ research-agent/ # ClaudeSyncClient multi-turn with hooks
The Java SDK mirrors the official Python Claude Agent SDK. Current feature parity status:
| Feature | Python | Java | Notes |
|---|---|---|---|
| Core APIs | |||
| One-shot queries | β | β | Query.text(), Query.execute() |
| Blocking client | β | β | ClaudeClient.sync() |
| Async client | β | β | ClaudeClient.async() (Reactor) |
| Multi-turn conversations | β | β | Context preserved across turns |
| Configuration | |||
| Model selection | β | β | .model() or CLIOptions |
| System prompt | β | β | .systemPrompt() |
| Append system prompt | β | β | .appendSystemPrompt() |
| Permission modes | β | β | PermissionMode enum |
| Allowed/disallowed tools | β | β | .allowedTools(), .disallowedTools() |
| Max turns | β | β | .maxTurns() |
| Max tokens | β | β | .maxTokens() |
| Extensibility | |||
| Hook system (PreToolUse) | β | β | HookRegistry.registerPreToolUse() |
| Hook system (PostToolUse) | β | β | HookRegistry.registerPostToolUse() |
| MCP server integration | β | β | External + in-process servers |
| Permission callbacks | β | β | ToolPermissionCallback |
| Agent definitions | β | β | AgentDefinition for subagents |
| Advanced | |||
| File checkpointing | β | β | Not yet implemented |
Beta features (--betas) |
β | β | Not yet implemented |
| Sandbox settings | β | β | Not yet implemented |
-
Reactive Streaming: Java SDK uses Project Reactor (Flux/Mono) for reactive streams, while Python uses async generators.
-
Factory Pattern: Java follows the MCP Java SDK pattern with
ClaudeClient.sync()/ClaudeClient.async()factory methods. -
Iterator vs Iterable:
ClaudeSyncClient.receiveResponse()returnsIterator<ParsedMessage>(notIterable), requiringwhile (response.hasNext())pattern. -
Type Safety: Java SDK leverages sealed interfaces and pattern matching for message type handling.
Apache License 2.0
Contributions are welcome! Please open an issue or submit a pull request.