15 KiB
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:
```xml com.anthropic anthropic-java 2.16.0 ```
Gradle:
```groovy implementation("com.anthropic:anthropic-java:2.16.0") ```
Client Initialization
```java import com.anthropic.client.AnthropicClient; import com.anthropic.client.okhttp.AnthropicOkHttpClient;
// 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
```java import com.anthropic.models.messages.MessageCreateParams; import com.anthropic.models.messages.Message; import com.anthropic.models.messages.Model;
MessageCreateParams params = MessageCreateParams.builder() .model(Model.CLAUDE_OPUS_4_6) .maxTokens(1024L) .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
```java import com.anthropic.core.http.StreamResponse; import com.anthropic.models.messages.RawMessageStreamEvent;
MessageCreateParams params = MessageCreateParams.builder() .model(Model.CLAUDE_OPUS_4_6) .maxTokens(1024L) .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.
```java import com.anthropic.models.messages.ContentBlock; import com.anthropic.models.messages.MessageCreateParams; import com.anthropic.models.messages.Model; import com.anthropic.models.messages.ThinkingConfigAdaptive;
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)
```java import com.anthropic.models.beta.messages.MessageCreateParams; import com.anthropic.models.beta.messages.BetaMessage; import com.anthropic.helpers.BetaToolRunner; import com.fasterxml.jackson.annotation.JsonClassDescription; import com.fasterxml.jackson.annotation.JsonPropertyDescription; import java.util.function.Supplier;
@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(1024L) .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.
```java import com.anthropic.helpers.BetaMemoryToolHandler; import com.anthropic.helpers.BetaToolRunner; import com.anthropic.models.beta.messages.BetaMemoryTool20250818; import com.anthropic.models.beta.messages.BetaMessage; import com.anthropic.models.beta.messages.MessageCreateParams; import com.anthropic.models.beta.messages.ToolRunnerCreateParams;
// 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.
```java import com.anthropic.core.JsonValue; import com.anthropic.models.messages.Tool;
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(1024L) .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`:
```java import com.anthropic.models.messages.MessageParam; import com.anthropic.models.messages.ContentBlockParam; import com.anthropic.models.messages.ToolResultBlockParam;
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`.
```java import com.anthropic.models.messages.OutputConfig;
.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.
```java import com.anthropic.models.messages.TextBlockParam; import com.anthropic.models.messages.CacheControlEphemeral;
.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()`.
Token Counting
```java import com.anthropic.models.messages.MessageCountTokensParams;
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.
```java import com.anthropic.models.messages.StructuredMessageCreateParams;
record Book(String title, String author) {} record BookList(List books) {}
StructuredMessageCreateParams params = MessageCreateParams.builder() .model(Model.CLAUDE_SONNET_4_6) .maxTokens(2048L) .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()`.
```java import com.anthropic.models.messages.DocumentBlockParam; import com.anthropic.models.messages.ContentBlockParam; import com.anthropic.models.messages.TextBlockParam;
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.
```java import com.anthropic.models.messages.WebSearchTool20260209; import com.anthropic.models.messages.ToolBash20250124; import com.anthropic.models.messages.ToolTextEditor20250728; import com.anthropic.models.messages.CodeExecutionTool20260120;
.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()`:
```java import com.anthropic.models.beta.messages.MessageCreateParams; import com.anthropic.models.beta.messages.BetaToolBash20250124; import com.anthropic.models.beta.messages.BetaCodeExecutionTool20260120; import com.anthropic.models.beta.messages.BetaRequestMcpServerUrlDefinition;
MessageCreateParams params = MessageCreateParams.builder() .model(Model.CLAUDE_OPUS_4_6) .maxTokens(1024L) .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:
```java 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).
```java import com.anthropic.models.beta.files.FileUploadParams; import com.anthropic.models.beta.files.FileMetadata; import com.anthropic.models.beta.messages.BetaRequestDocumentBlock; import java.nio.file.Paths;
FileMetadata meta = client.beta().files().upload( FileUploadParams.builder() .file(Paths.get("/path/to/doc.pdf")) // or .file(InputStream) or .file(byte[]) .build());
// Reference in a beta message: BetaRequestDocumentBlock doc = BetaRequestDocumentBlock.builder() .fileSource(meta.id()) .build(); ```
Other methods: `.list()`, `.delete(String fileId)`, `.download(String fileId)`, `.retrieveMetadata(String fileId)`.