Documentation Index
Fetch the complete documentation index at: https://springaicommunity.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Complete API reference for the Claude Agent SDK Java, including all classes, methods, and types.
This documentation follows the structure of the official Python SDK documentation.
Installation
Maven
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>claude-code-sdk</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
Gradle
implementation 'org.springaicommunity:claude-code-sdk:1.0.0-SNAPSHOT'
Three-API Architecture
The Java SDK provides three ways to interact with Claude Code:
Quick Comparison
| Feature | Query | ClaudeSyncClient | ClaudeAsyncClient |
|---|
| Style | Static methods | Iterator-based | Flux/Mono (Reactor) |
| Session | New each call | Reuses same session | Reuses same session |
| Conversation | Single exchange | Multi-turn | Multi-turn |
| Hooks | Not supported | Supported | Supported |
| MCP Servers | Not supported | Supported | Supported |
| Permission Callbacks | Not supported | Supported | Supported |
| Use Case | CLI tools, scripts | Traditional apps | Non-blocking apps |
Both ClaudeSyncClient and ClaudeAsyncClient provide the same capabilities. They differ only in programming paradigm (blocking vs non-blocking).
When to Use Query
Best for:
- One-off questions where you don’t need conversation history
- Simple automation scripts
- CLI tools and batch processing
When to Use ClaudeSyncClient
Best for:
- Multi-turn conversations with context
- Tool interception with hooks
- MCP server integration
- Standard Java iteration patterns
When to Use ClaudeAsyncClient
Best for:
- Spring WebFlux applications
- Server-Sent Events (SSE) streaming
- Non-blocking I/O requirements
- Reactive pipelines
Query - One-Shot API
Creates a new session for each interaction. Provides static methods for simple queries.
Methods
| Method | Return Type | Description |
|---|
text(prompt) | String | Simple one-liner, returns text response |
text(prompt, options) | String | With configuration options |
execute(prompt) | QueryResult | Full result with metadata |
execute(prompt, options) | QueryResult | With configuration options |
query(prompt) | Iterable<Message> | Iterate over messages |
stream(prompt) | Stream<Message> | Java Stream over messages |
Example - Simple query
import org.springaicommunity.claude.agent.sdk.Query;
// Simplest usage - one line
String answer = Query.text("What is 2+2?");
System.out.println(answer); // "4"
Example - With options
import org.springaicommunity.claude.agent.sdk.Query;
import org.springaicommunity.claude.agent.sdk.QueryOptions;
import java.time.Duration;
QueryOptions options = QueryOptions.builder()
.model("claude-sonnet-4-20250514")
.appendSystemPrompt("Be concise")
.timeout(Duration.ofMinutes(5))
.build();
String response = Query.text("Explain Java", options);
System.out.println(response);
import org.springaicommunity.claude.agent.sdk.Query;
import org.springaicommunity.claude.agent.sdk.types.QueryResult;
QueryResult result = Query.execute("Write a haiku about Java");
System.out.println(result.text().orElse(""));
System.out.println("Cost: $" + result.metadata().cost().calculateTotal());
System.out.println("Duration: " + result.metadata().getDuration().toMillis() + "ms");
System.out.println("Model: " + result.metadata().model());
Example - Streaming iteration
for (Message msg : Query.query("Explain recursion")) {
if (msg instanceof AssistantMessage am) {
am.getTextContent().ifPresent(System.out::print);
}
}
// Or with Stream API
Query.stream("Explain recursion")
.filter(msg -> msg instanceof AssistantMessage)
.forEach(msg -> System.out.println(msg));
ClaudeClient - Factory
Factory class for creating sync and async clients.
Methods
| Method | Return Type | Description |
|---|
sync() | ClaudeSyncClient.Builder | Create blocking client builder |
sync(options) | ClaudeSyncClient.Builder | Create builder with pre-configured CLIOptions |
async() | ClaudeAsyncClient.Builder | Create reactive client builder |
async(options) | ClaudeAsyncClient.Builder | Create builder with pre-configured CLIOptions |
Example - Factory pattern
// Sync client with fluent builder
try (ClaudeSyncClient client = ClaudeClient.sync()
.workingDirectory(Path.of("."))
.model("claude-sonnet-4-20250514")
.systemPrompt("You are helpful")
.timeout(Duration.ofMinutes(5))
.build()) {
// Use client
}
// Async client
ClaudeAsyncClient client = ClaudeClient.async()
.workingDirectory(Path.of("."))
.permissionMode(PermissionMode.BYPASS_PERMISSIONS)
.build();
ClaudeSyncClient - Blocking Client
Maintains a conversation session across multiple exchanges. Uses iterator-based message handling.
Methods
| Method | Description |
|---|
connect(prompt) | Start session with initial prompt |
query(prompt) | Send follow-up message |
receiveResponse() | Get iterator over response messages |
interrupt() | Stop current operation |
close() | End session and release resources |
Example - Multi-turn conversation
import org.springaicommunity.claude.agent.sdk.ClaudeClient;
import org.springaicommunity.claude.agent.sdk.ClaudeSyncClient;
import org.springaicommunity.claude.agent.sdk.parsing.ParsedMessage;
import org.springaicommunity.claude.agent.sdk.types.AssistantMessage;
import java.util.Iterator;
try (ClaudeSyncClient client = ClaudeClient.sync()
.workingDirectory(Path.of("."))
.build()) {
// First turn
client.connect("My favorite color is blue. Remember this.");
Iterator<ParsedMessage> response = client.receiveResponse();
while (response.hasNext()) {
ParsedMessage msg = response.next();
if (msg.isRegularMessage() && msg.asMessage() instanceof AssistantMessage am) {
am.getTextContent().ifPresent(System.out::println);
}
}
// Second turn - Claude remembers context
client.query("What is my favorite color?");
response = client.receiveResponse();
while (response.hasNext()) {
ParsedMessage msg = response.next();
if (msg.isRegularMessage() && msg.asMessage() instanceof AssistantMessage am) {
am.getTextContent().ifPresent(System.out::println); // "blue"
}
}
}
Example - With hooks
import org.springaicommunity.claude.agent.sdk.hooks.HookRegistry;
import org.springaicommunity.claude.agent.sdk.hooks.HookInput;
import org.springaicommunity.claude.agent.sdk.hooks.HookOutput;
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();
});
// Log all tool results
hookRegistry.registerPostToolUse(input -> {
if (input instanceof HookInput.PostToolUseInput postToolUse) {
System.out.println("Tool completed: " + postToolUse.toolName());
}
return HookOutput.allow();
});
try (ClaudeSyncClient client = ClaudeClient.sync()
.workingDirectory(Path.of("."))
.permissionMode(PermissionMode.DEFAULT)
.hookRegistry(hookRegistry)
.build()) {
// Hooks intercept tool calls
}
Example - With MCP servers
import org.springaicommunity.claude.agent.sdk.mcp.McpServerConfig;
// External MCP server (subprocess)
McpServerConfig npmServer = McpServerConfig.command("npx")
.args("-y", "@anthropic/mcp-server-filesystem")
.env("HOME", System.getProperty("user.home"))
.build();
// In-process SDK MCP server
McpServerConfig sdkServer = McpServerConfig.sdk(myMcpServer);
try (ClaudeSyncClient client = ClaudeClient.sync()
.workingDirectory(Path.of("."))
.mcpServer("filesystem", npmServer)
.mcpServer("custom", sdkServer)
.build()) {
// MCP tools available to Claude
}
ClaudeAsyncClient - Reactive Client
Non-blocking reactive API using Project Reactor. Use when you need composable, non-blocking chains.
Methods
| Method | Return Type | Description |
|---|
connect(prompt) | TurnSpec | Start session with prompt |
query(prompt) | TurnSpec | Send follow-up message |
onMessage(handler) | ClaudeAsyncClient | Register message handler |
onResult(handler) | ClaudeAsyncClient | Register result handler |
close() | Mono<Void> | End session |
TurnSpec Methods
| Method | Return Type | Description |
|---|
.text() | Mono<String> | Collected text, enables flatMap chaining |
.textStream() | Flux<String> | Streaming text |
.messages() | Flux<Message> | All message types |
Example - Text response with TurnSpec
ClaudeAsyncClient client = ClaudeClient.async()
.workingDirectory(Path.of("."))
.permissionMode(PermissionMode.BYPASS_PERMISSIONS)
.build();
// Simple text response
client.connect("Hello!").text()
.doOnSuccess(System.out::println)
.subscribe(); // Non-blocking
Example - Multi-turn with flatMap chaining
// Elegant multi-turn via flatMap
client.connect("My favorite color is blue.").text()
.flatMap(r1 -> client.query("What is my favorite color?").text())
.doOnSuccess(System.out::println) // "blue"
.subscribe();
Example - Text streaming
// Stream text as it arrives
client.query("Explain recursion").textStream()
.doOnNext(System.out::print)
.subscribe();
Example - Full message access
// Access all message types for metadata
client.query("List files").messages()
.doOnNext(msg -> {
if (msg instanceof AssistantMessage am) {
am.text().ifPresent(System.out::println);
} else if (msg instanceof ResultMessage rm) {
System.out.printf("Cost: $%.6f%n", rm.totalCostUsd());
}
})
.subscribe();
Example - Spring WebFlux SSE endpoint
import org.springframework.web.bind.annotation.*;
import org.springframework.http.MediaType;
import reactor.core.publisher.Flux;
@RestController
public class ChatController {
private final ClaudeAsyncClient client;
public ChatController() {
this.client = ClaudeClient.async()
.workingDirectory(Path.of("."))
.permissionMode(PermissionMode.BYPASS_PERMISSIONS)
.build();
}
@GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chat(@RequestParam String message) {
return client.query(message).textStream();
}
}
Example - Cross-turn handlers
ClaudeAsyncClient client = ClaudeClient.async()
.workingDirectory(Path.of("."))
.build();
// Register handlers (cross-turn concerns)
client.onMessage(msg -> logger.info("Message: {}", msg.getClass().getSimpleName()))
.onResult(result -> logger.info("Turn complete, tokens: {}",
result.usage().inputTokens() + result.usage().outputTokens()));
// Handlers are called for ALL turns
client.queryAndReceive("First question").subscribe();
client.queryAndReceive("Second question").subscribe(); // Handlers still fire
Flux vs Handlers
| If you are… | Use |
|---|
| Printing or streaming output to a user | Flux |
| Building a WebFlux/SSE endpoint | Flux |
| Doing per-turn filtering or transformation | Flux |
| Logging tool usage across all turns | Handlers |
| Collecting metrics or tracing | Handlers |
| Running a long-lived agent loop | Handlers |
“If the logic belongs to a single turn, use Flux.
If the logic applies to the entire session, use handlers.”
Types
QueryOptions
Configuration for Query operations.
QueryOptions.builder()
.model("claude-sonnet-4-20250514") // Model selection
.systemPrompt("Custom system prompt") // Replace default
.appendSystemPrompt("Additional instructions") // Append to default
.timeout(Duration.ofMinutes(5)) // Query timeout
.allowedTools(List.of("Read", "Write")) // Restrict tools
.disallowedTools(List.of("Bash")) // Block tools
.maxTurns(10) // Limit agentic turns
.maxBudgetUsd(1.0) // Cost limit
.workingDirectory(Path.of("/project")) // Working directory
.build();
CLIOptions
Lower-level configuration for CLI transport. Use with ClaudeClient.sync(options) or ClaudeClient.async(options).
CLIOptions options = CLIOptions.builder()
.model("claude-sonnet-4-20250514")
.systemPrompt("You are helpful")
.allowedTools(List.of("Read", "Write"))
.maxTurns(10)
.build();
// When using pre-built CLIOptions, only session config is available on builder
try (ClaudeSyncClient client = ClaudeClient.sync(options)
.workingDirectory(Path.of("."))
.timeout(Duration.ofMinutes(5))
.build()) {
// .model() NOT available here - already in options
}
PermissionMode
public enum PermissionMode {
DEFAULT, // Standard permission behavior
ACCEPT_EDITS, // Auto-accept file edits
PLAN, // Planning mode - read-only
BYPASS_PERMISSIONS // Bypass all checks (use with caution)
}
HookRegistry
Register callbacks to intercept tool execution.
HookRegistry registry = new HookRegistry();
// Pre-tool hook - runs before tool executes
registry.registerPreToolUse("Bash", input -> {
// Return HookOutput.allow() or HookOutput.block("reason")
return HookOutput.allow();
});
// Post-tool hook - runs after tool completes
registry.registerPostToolUse(input -> {
// Log, trace, or modify behavior
return HookOutput.allow();
});
McpServerConfig
Configure MCP servers for tool integration.
// External process (npx, docker, etc.)
McpServerConfig external = McpServerConfig.command("npx")
.args("-y", "@anthropic/mcp-server-filesystem")
.env("HOME", System.getProperty("user.home"))
.build();
// In-process SDK server
McpServerConfig inProcess = McpServerConfig.sdk(myMcpServer);
Message Types
ParsedMessage
Sealed interface wrapping messages from the CLI.
public sealed interface ParsedMessage permits
ParsedMessage.RegularMessage,
ParsedMessage.Control,
ParsedMessage.ControlResponseMessage,
ParsedMessage.EndOfStream {
boolean isRegularMessage(); // User, Assistant, System, Result
boolean isControlRequest(); // Control protocol request
boolean isControlResponse(); // Control protocol response
Message asMessage(); // Get as Message, or null
ControlRequest asControlRequest();
}
Processing Messages
Iterator<ParsedMessage> response = client.receiveResponse();
while (response.hasNext()) {
ParsedMessage parsed = response.next();
if (parsed.isRegularMessage()) {
Message message = parsed.asMessage();
if (message instanceof AssistantMessage am) {
System.out.println(am.getTextContent().orElse(""));
} else if (message instanceof ResultMessage result) {
System.out.println("Done. Cost: $" + result.totalCostUsd());
}
}
}
Message
Base interface for regular messages.
public sealed interface Message permits UserMessage, AssistantMessage, ResultMessage
AssistantMessage
public record AssistantMessage(
List<ContentBlock> content,
String model
) implements Message {
public Optional<String> getTextContent();
}
ResultMessage
public record ResultMessage(
String subtype,
long durationMs,
long durationApiMs,
boolean isError,
int numTurns,
String sessionId,
Double totalCostUsd,
Usage usage,
String result
) implements Message
Content Block Types
public sealed interface ContentBlock permits TextBlock, ThinkingBlock, ToolUseBlock, ToolResultBlock
TextBlock
public record TextBlock(String text) implements ContentBlock
public record ToolUseBlock(
String id,
String name,
Map<String, Object> input
) implements ContentBlock
Error Types
public class ClaudeSDKException extends RuntimeException {
// Base exception for all SDK errors
}
public class TransportException extends ClaudeSDKException {
// Raised when CLI transport fails
}
public class SessionClosedException extends ClaudeSDKException {
// Raised when operating on a closed session
}
public class MessageParseException extends ClaudeSDKException {
// Raised when message parsing fails
}
See also