Claude API — Java
# Claude API — Java
Overview
Claude API — Java
Note: The Java SDK supports the Claude API and beta tool use with annotated classes. Agent SDK is not yet available for Java.
Installation
Maven:
<dependency>
<groupId>com.anthropic</groupId>
<artifactId>anthropic-java</artifactId>
<version>2.17.0</version>
</dependency>
Gradle:
implementation("com.anthropic:anthropic-java:2.17.0")
Client Initialization
// Default (reads ANTHROPIC_API_KEY from environment)
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Explicit API key
AnthropicClient client = AnthropicOkHttpClient.builder()
.apiKey("your-api-key")
.build();
Basic Message Request
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_6)
.maxTokens(16000L)
.addUserMessage("What is the capital of France?")
.build();
Message response = client.messages().create(params);
response.content().stream()
.flatMap(block -> block.text().stream())
.forEach(textBlock -> System.out.println(textBlock.text()));
Streaming
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_6)
.maxTokens(64000L)
.addUserMessage("Write a haiku")
.build();
try (StreamResponse streamResponse = client.messages().createStreaming(params)) {
streamResponse.stream()
.flatMap(event -> event.contentBlockDelta().stream())
.flatMap(deltaEvent -> deltaEvent.delta().text().stream())
.forEach(textDelta -> System.out.print(textDelta.text()));
}
Thinking
Adaptive thinking is the recommended mode for Claude 4.6+ models. Claude decides dynamically when and how much to think. The builder has a direct .thinking(ThinkingConfigAdaptive) overload — no manual union wrapping.
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(16000L)
.thinking(ThinkingConfigAdaptive.builder().build())
.addUserMessage("Solve this step by step: 27 * 453")
.build();
for (ContentBlock block : client.messages().create(params).content()) {
block.thinking().ifPresent(t -> System.out.println("[thinking] " + t.thinking()));
block.text().ifPresent(t -> System.out.println(t.text()));
}
Deprecated:
ThinkingConfigEnabled.builder().budgetTokens(N)(and the.enabledThinking(N)shortcut) still works on Claude 4.6 but is deprecated. Use adaptive thinking above.
ContentBlock narrowing: .thinking() / .text() return Optional — use .ifPresent(...) or .stream().flatMap(...). Alternative: isThinking() / asThinking() boolean+unwrap pairs (throws on wrong variant).
Tool Use (Beta)
The Java SDK supports beta tool use with annotated classes. Tool classes implement Supplier for automatic execution via BetaToolRunner.
Tool Runner (automatic loop)
@JsonClassDescription("Get the weather in a given location")
static class GetWeather implements Supplier {
@JsonPropertyDescription("The city and state, e.g. San Francisco, CA")
public String location;
@Override
public String get() {
return "The weather in " + location + " is sunny and 72°F";
}
}
BetaToolRunner toolRunner = client.beta().messages().toolRunner(
MessageCreateParams.builder()
.model("{{OPUS_ID}}")
.maxTokens(16000L)
.putAdditionalHeader("anthropic-beta", "structured-outputs-2025-11-13")
.addTool(GetWeather.class)
.addUserMessage("What's the weather in San Francisco?")
.build());
for (BetaMessage message : toolRunner) {
System.out.println(message);
}
Memory Tool
The Java SDK provides BetaMemoryToolHandler for implementing the memory tool backend. You supply a handler that manages file storage, and the BetaToolRunner handles memory tool calls automatically.
// Implement BetaMemoryToolHandler with your storage backend (e.g., filesystem)
BetaMemoryToolHandler memoryHandler = new FileSystemMemoryToolHandler(sandboxRoot);
MessageCreateParams createParams = MessageCreateParams.builder()
.model("{{OPUS_ID}}")
.maxTokens(4096L)
.addTool(BetaMemoryTool20250818.builder().build())
.addUserMessage("Remember that my favorite color is blue")
.build();
BetaToolRunner toolRunner = client.beta().messages().toolRunner(
ToolRunnerCreateParams.builder()
.betaMemoryToolHandler(memoryHandler)
.initialMessageParams(createParams)
.build());
for (BetaMessage message : toolRunner) {
System.out.println(message);
}
See the shared memory tool concepts for more details on the memory tool.
Non-Beta Tool Declaration (manual JSON schema)
Tool.InputSchema.Properties is a freeform Map<String, JsonValue> wrapper — build property schemas via putAdditionalProperty. type: "object" is the default. The builder has a direct .addTool(Tool) overload that wraps in ToolUnion automatically.
Tool tool = Tool.builder()
.name("get_weather")
.description("Get the current weather in a given location")
.inputSchema(Tool.InputSchema.builder()
.properties(Tool.InputSchema.Properties.builder()
.putAdditionalProperty("location", JsonValue.from(Map.of("type", "string")))
.build())
.required(List.of("location"))
.build())
.build();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(16000L)
.addTool(tool)
.addUserMessage("Weather in Paris?")
.build();
For manual tool loops, handle tool_use blocks in the response, send tool_result back, loop until stop_reason is "end_turn". See shared tool use concepts.
Building MessageParam with Content Blocks (Tool Result Round-Trip)
MessageParam.Content is an inner union class (string | list). Use the builder's .contentOfBlockParams(List) alias — there is NO separate MessageParamContent class with a static ofBlockParams:
List results = List.of(
ContentBlockParam.ofToolResult(ToolResultBlockParam.builder()
.toolUseId(toolUseBlock.id())
.content(yourResultString)
.build())
);
MessageParam toolResultMsg = MessageParam.builder()
.role(MessageParam.Role.USER)
.contentOfBlockParams(results) // builder alias for Content.ofBlockParams(...)
.build();
Effort Parameter
Effort is nested inside OutputConfig — there is NO .effort() directly on MessageCreateParams.Builder.
.outputConfig(OutputConfig.builder()
.effort(OutputConfig.Effort.HIGH) // or LOW, MEDIUM, MAX
.build())
Combine with Thinking = ThinkingConfigAdaptive for cost-quality control.
Prompt Caching
System message as a list of TextBlockParam with CacheControlEphemeral. Use .systemOfTextBlockParams(...) — the plain .system(String) overload can't carry cache control. For placement patterns and the silent-invalidator audit checklist, see shared/prompt-caching.md.
.systemOfTextBlockParams(List.of(
TextBlockParam.builder()
.text(longSystemPrompt)
.cacheControl(CacheControlEphemeral.builder()
.ttl(CacheControlEphemeral.Ttl.TTL_1H) // optional; also TTL_5M
.build())
.build()))
There's also a top-level .cacheControl(CacheControlEphemeral) on MessageCreateParams.Builder and on Tool.builder().
Verify hits via response.usage().cacheCreationInputTokens() / response.usage().cacheReadInputTokens().
Token Counting
long tokens = client.messages().countTokens(
MessageCountTokensParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.addUserMessage("Hello")
.build()
).inputTokens();
Structured Output
The class-based overload auto-derives the JSON schema from your POJO and gives you a typed .text() return — no manual schema, no manual parsing.
record Book(String title, String author) {}
record BookList(List books) {}
StructuredMessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_SONNET_4_6)
.maxTokens(16000L)
.outputConfig(BookList.class) // returns a typed builder
.addUserMessage("List 3 classic novels")
.build();
client.messages().create(params).content().stream()
.flatMap(cb -> cb.text().stream())
.forEach(typed -> {
// typed.text() returns BookList, not String
for (Book b : typed.text().books()) System.out.println(b.title());
});
Supports Jackson annotations: @JsonPropertyDescription, @JsonIgnore, @ArraySchema(minItems=...). Manual schema path: OutputConfig.builder().format(JsonOutputFormat.builder().schema(...).build()).
PDF / Document Input
DocumentBlockParam builder has source shortcuts. Wrap in ContentBlockParam.ofDocument() and pass via .addUserMessageOfBlockParams().
DocumentBlockParam doc = DocumentBlockParam.builder()
.base64Source(base64String) // or .urlSource("https://...") or .textSource("...")
.title("My Document") // optional
.build();
.addUserMessageOfBlockParams(List.of(
ContentBlockParam.ofDocument(doc),
ContentBlockParam.ofText(TextBlockParam.builder().text("Summarize this").build())))
Server-Side Tools
Version-suffixed types; name/type auto-set by builder. Direct .addTool() overloads exist for every type — no manual ToolUnion wrapping.
.addTool(WebSearchTool20260209.builder()
.maxUses(5L) // optional
.allowedDomains(List.of("example.com")) // optional
.build())
.addTool(ToolBash20250124.builder().build())
.addTool(ToolTextEditor20250728.builder().build())
.addTool(CodeExecutionTool20260120.builder().build())
Also available: WebFetchTool20260209, MemoryTool20250818, ToolSearchToolBm25_20251119.
Beta namespace (MCP, compaction)
For beta-only features use com.anthropic.models.beta.messages.* — class names have a Beta prefix AND live in the beta package. The beta MessageCreateParams.Builder has direct .addTool(BetaToolBash20250124) overloads AND .addMcpServer():
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_4_6)
.maxTokens(16000L)
.addBeta("mcp-client-2025-11-20")
.addTool(BetaToolBash20250124.builder().build())
.addTool(BetaCodeExecutionTool20260120.builder().build())
.addMcpServer(BetaRequestMcpServerUrlDefinition.builder()
.name("my-server")
.url("https://example.com/mcp")
.build())
.addUserMessage("...")
.build();
client.beta().messages().create(params);
BetaTool* types are NOT interchangeable with non-beta Tool* — pick one namespace per request.
Reading server-tool blocks in the response: ServerToolUseBlock has .id(), .name() (enum), and ._input() returning raw JsonValue — there is NO typed .input(). For code execution results, unwrap two levels:
for (ContentBlock block : response.content()) {
block.serverToolUse().ifPresent(stu -> {
System.out.println("tool: " + stu.name() + " input: " + stu._input());
});
block.codeExecutionToolResult().ifPresent(r -> {
r.content().resultBlock().ifPresent(result -> {
System.out.println("stdout: " + result.stdout());
System.out.println("stderr: " + result.stderr());
System.out.println("exit: " + result.returnCode());
});
});
}
Files API (Beta)
Under client.beta().files(). File references in messages need the beta message types (non-beta DocumentBlockParam.Source has no file-ID variant).
FileMetadata meta = client.beta().files().upload(
FileUploadParams.builder()
.file(Paths.get("/path/to/doc.pdf")) // or .file(InputStream) or .file(byte[])
.build());
// Re