Skip to main content

Anthropic

Maven Dependency

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-anthropic</artifactId>
<version>1.17.2</version>
</dependency>

AnthropicChatModel

AnthropicChatModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName(CLAUDE_3_5_SONNET_20240620)
.build();
String answer = model.chat("Say 'Hello World'");
System.out.println(answer);

Customizing AnthropicChatModel

AnthropicChatModel model = AnthropicChatModel.builder()
.httpClientBuilder(...)
.baseUrl(...)
.apiKey(...)
.version(...)
.beta(...)
.modelName(...)
.temperature(...)
.topP(...)
.topK(...)
.maxTokens(...)
.stopSequences(...)
.toolSpecifications(...)
.toolChoice(...)
.toolChoiceName(...)
.disableParallelToolUse(...)
.serverTools(...)
.returnServerToolResults(...)
.toolMetadataKeysToSend(...)
.cacheSystemMessages(...)
.cacheTools(...)
.returnCacheDiagnostics(...)
.thinkingType(...)
.thinkingBudgetTokens(...)
.thinkingDisplay(...)
.returnThinking(...)
.sendThinking(...)
.midConversationSystemMessages(...)
.timeout(...)
.maxRetries(...)
.logRequests(...)
.logResponses(...)
.listeners(...)
// You can also specify default chat request parameters using ChatRequestParameters or AnthropicChatRequestParameters
.defaultRequestParameters(...)
.userId(...)
.customParameters(...)
.build();

See the description of some of the parameters above here.

Per-Request Parameters

The Anthropic-specific options shown above (cacheSystemMessages, cacheTools, returnCacheDiagnostics, thinkingType, thinkingBudgetTokens, sendThinking, returnThinking, midConversationSystemMessages, toolChoiceName, disableParallelToolUse and userId), as well as previousMessageId (request-only, see Cache Diagnostics), can also be set per request via AnthropicChatRequestParameters, overriding the values configured on the model builder. This lets a single shared model instance vary these options from one call to the next — for example, enabling prompt caching for a long-running agent loop while skipping it for a cheap one-shot completion, without building a second model:

AnthropicChatModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName(CLAUDE_3_5_SONNET_20240620)
.build();

AnthropicChatRequestParameters parameters = AnthropicChatRequestParameters.builder()
.cacheSystemMessages(true)
.cacheTools(true)
.build();

ChatRequest chatRequest = ChatRequest.builder()
.messages(systemMessage, userMessage)
.parameters(parameters)
.build();

ChatResponse chatResponse = model.chat(chatRequest);

Any parameter not set on the request falls back to the value configured on the model builder.

AnthropicStreamingChatModel

AnthropicStreamingChatModel model = AnthropicStreamingChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName(CLAUDE_3_5_SONNET_20240620)
.build();

model.chat("Say 'Hello World'", new StreamingChatResponseHandler() {

@Override
public void onPartialResponse(String partialResponse) {
// this method is called when a new partial response is available. It can consist of one or more tokens.
}

@Override
public void onCompleteResponse(ChatResponse completeResponse) {
// this method is called when the model has completed responding
}

@Override
public void onError(Throwable error) {
// this method is called when an error occurs
}
});

Customizing AnthropicStreamingChatModel

Identical to the AnthropicChatModel, see above.

Tools

Anthropic supports tools in both streaming and non-streaming mode.

Anthropic documentation on tools can be found here.

Tool Choice

Anthropic's tool choice feature is available for both streaming and non-streaming interactions by setting toolChoice(ToolChoice) or toolChoiceName(String).

Parallel Tool Use

By default, Anthropic Claude may use multiple tools to answer a user query, but you can disable parallel tool by setting disableParallelToolUse(true).

Server Tools

Anthropic's server tools are supported via serverTools parameter, here is an example of using a web search tool:

AnthropicServerTool webSearchTool = AnthropicServerTool.builder()
.type("web_search_20250305")
.name("web_search")
.addAttribute("max_uses", 5)
.addAttribute("allowed_domains", List.of("accuweather.com"))
.build();

ChatModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName("claude-sonnet-4-5")
.serverTools(webSearchTool)
.logRequests(true)
.logResponses(true)
.build();

String answer = model.chat("What is the weather in Munich?");

Tools specified via serverTools will be included in every request to the Anthropic API.

Retrieving Server Tool Results

To access the raw results from server tools (e.g., web search results, code execution output, fileIds from generated files), enable returnServerToolResults(true). The results will be available in AiMessage.attributes() under the key "server_tool_results":

ChatModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName("claude-sonnet-4-5")
.serverTools(webSearchTool)
.returnServerToolResults(true)
.build();

ChatResponse response = model.chat("What is the weather in Munich?");
AiMessage aiMessage = response.aiMessage();

List<AnthropicServerToolResult> results = aiMessage.attribute("server_tool_results", List.class);
for (AnthropicServerToolResult result : results) {
System.out.println("Type: " + result.type());
System.out.println("Tool Use ID: " + result.toolUseId());
System.out.println("Content: " + result.content());
}

This is disabled by default to avoid storing potentially large data in ChatMemory.

Skills

Anthropic's Agent Skills let Claude generate real downloadable documents (.xlsx, .pptx, .docx, .pdf) by running pre-built skills inside the code execution container. Enable them via the typed skills parameter:

AnthropicChatModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName("claude-opus-4-8")
.maxTokens(4096)
.beta("code-execution-2025-08-25,skills-2025-10-02,files-api-2025-04-14")
.skills(AnthropicSkill.XLSX, AnthropicSkill.PPTX)
.returnServerToolResults(true)
.build();

ChatResponse response = model.chat("Create an Excel spreadsheet with the numbers 1 to 5 in column A");

Enabling skills automatically:

  • adds the container.skills block to the request,
  • adds the required code_execution server tool (unless one is already configured via serverTools(...)).

You must opt into the required beta features yourself via beta(...), as shown above. These are beta headers and their values change over time, so they are not injected for you — check the Agent Skills documentation for the current set.

Combine with returnServerToolResults(true) to surface the generated file ids under the "server_tool_results" key of AiMessage.attributes() (see Retrieving Server Tool Results above); the files are downloadable for 24 hours through Anthropic's Files API.

Skills are supported on Claude Sonnet 4 / 4.5, Opus 4 and later. At most 8 skills may be enabled per request. The same skills(...) parameter is available on AnthropicStreamingChatModel.

Tool Search Tool

Anthropic's tool search tool is supported via serverTools, tool metadata and toolMetadataKeysToSend parameters.

Here is an example when using high-level AI Service and @Tool APIs:

AnthropicServerTool toolSearchTool = AnthropicServerTool.builder()
.type("tool_search_tool_regex_20251119")
.name("tool_search_tool_regex")
.build();

class Tools {

@Tool(metadata = "{\"defer_loading\": true}")
String getWeather(String location) {
return "sunny";
}

@Tool
String getTime(String location) {
return "12:34:56";
}
}

ChatModel chatModel = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName(CLAUDE_SONNET_4_5_20250929)
.beta("advanced-tool-use-2025-11-20")
.serverTools(toolSearchTool)
.toolMetadataKeysToSend("defer_loading") // need to specify it explicitly
.logRequests(true)
.logResponses(true)
.build();

interface Assistant {

@SystemMessage("Use tool search if needed")
String chat(String userMessage);
}

Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.tools(new Tools())
.build();

assistant.chat("What is the weather in Munich?");

Here is an example when using low-level ChatModel and ToolSpecification APIs:

AnthropicServerTool toolSearchTool = AnthropicServerTool.builder()
.type("tool_search_tool_regex_20251119")
.name("tool_search_tool_regex")
.build();

Map<String, Object> toolMetadata = Map.of("defer_loading", true);

ToolSpecification weatherTool = ToolSpecification.builder()
.name("get_weather")
.parameters(JsonObjectSchema.builder()
.addStringProperty("location")
.required("location")
.build())
.metadata(toolMetadata)
.build();

ToolSpecification timeTool = ToolSpecification.builder()
.name("get_time")
.parameters(JsonObjectSchema.builder()
.addStringProperty("location")
.required("location")
.build())
.build();

ChatModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName(CLAUDE_SONNET_4_5_20250929)
.beta("advanced-tool-use-2025-11-20")
.serverTools(toolSearchTool)
.toolMetadataKeysToSend(toolMetadata.keySet()) // need to specify it explicitly
.logRequests(true)
.logResponses(true)
.build();

ChatRequest chatRequest = ChatRequest.builder()
.messages(UserMessage.from("What is the weather in Munich? Use tool search if needed."))
.toolSpecifications(weatherTool, timeTool)
.build();

ChatResponse chatResponse = model.chat(chatRequest);

Programmatic Tool Calling

Anthropic's programmatic tool calling is supported via serverTools, tool metadata and toolMetadataKeysToSend parameters.

Here is an example when using high-level AI Service and @Tool APIs:

AnthropicServerTool codeExecutionTool = AnthropicServerTool.builder()
.type("code_execution_20250825")
.name("code_execution")
.build();

class Tools {

static final String TOOL_METADATA = "{\"allowed_callers\": [\"code_execution_20250825\"]}";
static final String TOOL_DESCRIPTION = """
Returns daily minimum and maximum temperatures recorded
for a specified city for a specified number of previous days.
Response format: [{"min":0.0,"max":10.0},{"min":0.0,"max":20.0},{"min":0.0,"max":30.0}]
""";

record TemperatureRange(double min, double max) {}

@Tool(value = TOOL_DESCRIPTION, metadata = TOOL_METADATA)
List<TemperatureRange> getDailyTemperatures(String city, int days) {
if ("Munich".equals(city) && days == 5) {
return List.of(
new TemperatureRange(0.0, 1.0),
new TemperatureRange(0.0, 2.0),
new TemperatureRange(0.0, 3.0),
new TemperatureRange(0.0, 4.0),
new TemperatureRange(0.0, 5.0)
);
}

throw new IllegalArgumentException("Unknown city: " + city + " or days: " + days);
}

@Tool(value = "Calculates the average of the specified list of numbers", metadata = TOOL_METADATA)
Double average(List<Double> numbers) {
return numbers.stream()
.mapToDouble(Double::doubleValue)
.average()
.orElseThrow();
}
}

ChatModel chatModel = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName(CLAUDE_SONNET_4_5_20250929)
.beta("advanced-tool-use-2025-11-20")
.serverTools(codeExecutionTool)
.toolMetadataKeysToSend("allowed_callers") // need to specify it explicitly
.logRequests(true)
.logResponses(true)
.build();

interface Assistant {

String chat(String userMessage);
}

Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.tools(new Tools())
.build();

assistant.chat("What was the average max temperature in Munich in the last 5 days?");

Check Tool Search Tool section to see an example of specifying tool metadata in the low-level ToolSpecification API.

Tool Use Examples

Anthropic's tool use examples are supported via tool metadata and toolMetadataKeysToSend parameters.

Here is an example when using high-level AI Service and @Tool APIs:

enum Unit {
CELSIUS, FAHRENHEIT
}

class Tools {

// NOTE: if javac "-parameters" option is not enabled, you need to change "location" to "arg0"
// and "unit" to "arg1" inside the TOOL_METADATA to make it work.
public static final String TOOL_METADATA = """
{
"input_examples": [
{
"location": "San Francisco, CA",
"unit": "FAHRENHEIT"
},
{
"location": "Tokyo, Japan",
"unit": "CELSIUS"
},
{
"location": "New York, NY"
}
]
}
""";

@Tool(metadata = TOOL_METADATA)
String getWeather(String location, @P(description = "temperature unit", required = false) Unit unit) {
return "sunny";
}
}

ChatModel chatModel = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName(CLAUDE_SONNET_4_5_20250929)
.beta("advanced-tool-use-2025-11-20")
.toolMetadataKeysToSend("input_examples") // need to specify it explicitly
.logRequests(true)
.logResponses(true)
.build();

interface Assistant {

String chat(String userMessage);
}

Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.tools(new Tools())
.build();

assistant.chat("What is the weather in Munich in Fahrenheit?");

Check Tool Search Tool section to see an example of specifying tool metadata in the low-level ToolSpecification API.

Caching

AnthropicChatModel and AnthropicStreamingChatModel return AnthropicTokenUsage in the response, which contains cacheCreationInputTokens and cacheReadInputTokens.

More info on caching can be found here.

Caching System Messages and Tools

Caching of system messages and tools is disabled by default. It can be enabled by setting the cacheSystemMessages and cacheTools parameters, respectively.

When enabled, cache_control blocks will be added to the last system message and tool, respectively.

Caching Individual Messages

UserMessage, AiMessage, and ToolExecutionResultMessage can each be marked for caching by setting the cache_control attribute to ephemeral. The cache control marker is automatically applied to the last content block of the message (for ToolExecutionResultMessage, this is the tool_result block itself).

UserMessage exposes a mutable attributes map:

UserMessage userMessage = UserMessage.from("Hello cached world");
userMessage.attributes().put("cache_control", "ephemeral");

AiMessage and ToolExecutionResultMessage carry an immutable attributes map, so set it via toBuilder(). This is especially useful in an agentic tool-execution loop, where the conversation history grows on every turn: marking the last message of a turn as ephemeral lets subsequent, larger requests reuse the cached prefix instead of re-billing the whole growing history at full price.

AiMessage aiMessage = someAiMessage.toBuilder()
.attributes(Map.of("cache_control", "ephemeral"))
.build();

ToolExecutionResultMessage toolExecutionResultMessage = someToolExecutionResultMessage.toBuilder()
.attributes(Map.of("cache_control", "ephemeral"))
.build();

Cache Diagnostics

Anthropic's (beta) cache diagnostics feature reports why a prompt-cache hit was missed (model, system prompt, tools or message history changed), instead of only showing cacheReadInputTokens drop to zero.

It requires the cache-diagnosis-2026-04-07 beta header and is enabled via returnCacheDiagnostics. Pass previousMessageId as null on the first turn of a conversation to opt in, and the id of the previous response on every subsequent turn:

AnthropicChatModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.beta("cache-diagnosis-2026-04-07")
.returnCacheDiagnostics(true)
.build();

ChatResponse response1 = model.chat(ChatRequest.builder()
.messages(UserMessage.from("Summarize section 1."))
.build());
String previousMessageId = ((AnthropicChatResponseMetadata) response1.metadata()).id();

ChatResponse response2 = model.chat(ChatRequest.builder()
.messages(UserMessage.from("Summarize section 1."), UserMessage.from("Now summarize section 2."))
.parameters(AnthropicChatRequestParameters.builder()
// returnCacheDiagnostics is already enabled on the model above, so on subsequent turns
// you only need to supply the previousMessageId (it changes every turn).
.previousMessageId(previousMessageId)
.build())
.build());

AnthropicCacheDiagnostics diagnostics = ((AnthropicChatResponseMetadata) response2.metadata()).cacheDiagnostics();
if (diagnostics != null && diagnostics.cacheMissReasonType() != null) {
// e.g. "model_changed", "system_changed", "tools_changed", "messages_changed",
// "previous_message_not_found" or "unavailable"
System.out.println(diagnostics.cacheMissReasonType());
}

cacheDiagnostics() is null when diagnostics weren't requested or no divergence was found.

Thinking

Both AnthropicChatModel and AnthropicStreamingChatModel support extended thinking and adaptive thinking features.

It is controlled by the following parameters:

  • thinkingType and thinkingBudgetTokens: enable thinking, see more details here.
  • thinkingDisplay: controls how thinking content is returned. Valid values are "summarized" and "omitted".
  • returnThinking: controls whether to return thinking (if available) inside AiMessage.thinking() and whether to invoke StreamingChatResponseHandler.onPartialThinking() and TokenStream.onPartialThinking() callbacks when using BedrockStreamingChatModel. Disabled by default. If enabled, tinking signatures will also be stored and returned inside the AiMessage.attributes().
  • sendThinking: controls whether to send thinking and signatures stored in AiMessage to the LLM in follow-up requests. Enabled by default.

In order to configure effort parameter, set customParameters when building the model:

ChatModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName("claude-sonnet-4-7")
.customParameters(Map.of("output_config", Map.of("effort", "max")))
...
.build();

Here is an example of how to configure thinking:

ChatModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName("claude-sonnet-4-5-20250929")
.thinkingType("enabled")
.thinkingBudgetTokens(1024)
.maxTokens(1024 + 100)
.returnThinking(true)
.sendThinking(true)
.build();

Mid-Conversation System Messages

By default, every SystemMessage is folded into the top-level system prompt regardless of where it appears in the message list. This matches how Anthropic has always worked and is unchanged.

Claude Opus 4.8 additionally supports mid-conversation system messages: a SystemMessage that appears after the conversation has started can be sent inline as a system entry in the messages array, so it takes effect from that point in the conversation onward (for example, to change the assistant's instructions partway through a session). Enable this with midConversationSystemMessages(true):

AnthropicChatModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName("claude-opus-4-8")
.midConversationSystemMessages(true)
.build();

ChatResponse response = model.chat(ChatRequest.builder()
.messages(
SystemMessage.from("You are a helpful assistant."), // leading -> top-level "system" prompt
UserMessage.from("Hello"),
AiMessage.from("Hi! How can I help?"),
SystemMessage.from("From now on, answer only in French."), // mid-conversation -> inline
UserMessage.from("What is the capital of Spain?"))
.build());

When enabled, leading SystemMessages (those before the first user/assistant message) still populate the top-level system prompt; only those appearing after the conversation has started are sent inline. This is not just a convention — Anthropic requires it: a system message cannot be the first entry in the messages array, and the base system prompt belongs in the stable, cacheable prefix anyway. With the option disabled (the default), behaviour is unchanged and all SystemMessages go to the top-level system prompt.

It can also be set per request via AnthropicChatRequestParameters (see Per-Request Parameters).

note

Anthropic constrains where a mid-conversation system message may be placed: it must immediately follow a user turn (including a user turn carrying tool results), must precede an assistant turn or end the array, and must not sit between a tool_use block and its tool_result. Consecutive system messages are also not allowed. Note that, with the option disabled, langchain4j merges multiple SystemMessages into the top-level system field; with it enabled, two adjacent mid-conversation SystemMessages would be sent as consecutive inline system entries and rejected. langchain4j does not reorder or merge inline messages — it sends them at the position you provide — so an unsupported model or an invalid placement results in a 400 from the Anthropic API.

PDF Support

Anthropic Claude supports processing PDF documents. You can send PDFs either via URL or base64-encoded data.

Sending PDF via URL

UserMessage message = UserMessage.from(
PdfFileContent.from(URI.create("https://example.com/document.pdf")),
TextContent.from("What are the key findings in this document?")
);

ChatResponse response = model.chat(message);

Sending PDF via Base64

String base64Data = Base64.getEncoder().encodeToString(Files.readAllBytes(Path.of("document.pdf")));

UserMessage message = UserMessage.from(
PdfFileContent.from(base64Data, "application/pdf"),
TextContent.from("Summarize this document.")
);

ChatResponse response = model.chat(message);

More info on PDF support can be found here.

Setting custom chat request parameters

When building AnthropicChatModel and AnthropicStreamingChatModel, you can configure custom parameters for the chat request within the HTTP request's JSON body. Here is an example of how to enable context editing:

record Edit(String type) {}
record ContextManagement(List<Edit> edits) { }
Map<String, Object> customParameters = Map.of("context_management", new ContextManagement(List.of(new Edit("clear_tool_uses_20250919"))));

ChatModel model = AnthropicChatModel.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.modelName(CLAUDE_SONNET_4_5_20250929)
.beta("context-management-2025-06-27")
.customParameters(customParameters)
.logRequests(true)
.logResponses(true)
.build();

String answer = model.chat("Hi");

This will produce an HTTP request with the following body:

{
"model" : "claude-sonnet-4-5-20250929",
"messages" : [ {
"role" : "user",
"content" : [ {
"type" : "text",
"text" : "Hi"
} ]
} ],
"context_management" : {
"edits" : [ {
"type" : "clear_tool_uses_20250919"
} ]
}
}

Alternatively, custom parameters can also be specified as a structure of nested maps:

Map<String, Object> customParameters = Map.of(
"context_management",
Map.of("edits", List.of(Map.of("type", "clear_tool_uses_20250919")))
);

Accessing raw HTTP responses and Server-Sent Events (SSE)

When using AnthropicChatModel, you can access the raw HTTP response:

SuccessfulHttpResponse rawHttpResponse = ((AnthropicChatResponseMetadata) chatResponse.metadata()).rawHttpResponse();
System.out.println(rawHttpResponse.body());
System.out.println(rawHttpResponse.headers());
System.out.println(rawHttpResponse.statusCode());

When using AnthropicStreamingChatModel, you can access the raw HTTP response (see above) and raw Server-Sent Events:

List<ServerSentEvent> rawServerSentEvents = ((AnthropicChatResponseMetadata) chatResponse.metadata()).rawServerSentEvents();
System.out.println(rawServerSentEvents.get(0).data());
System.out.println(rawServerSentEvents.get(0).event());

AnthropicTokenCountEstimator

TokenCountEstimator tokenCountEstimator = AnthropicTokenCountEstimator.builder()
.modelName(CLAUDE_3_OPUS_20240229)
.apiKey(System.getenv("ANTHROPIC_API_KEY"))
.logRequests(true)
.logResponses(true)
.build();

List<ChatMessage> messages = List.of(...);

int tokenCount = tokenCountEstimator.estimateTokenCountInMessages(messages);

Quarkus

See more details here.

Spring Boot

Import Spring Boot starter for Anthropic:

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-anthropic-spring-boot-starter</artifactId>
<version>1.17.2-beta27</version>
</dependency>

Configure AnthropicChatModel bean:

langchain4j.anthropic.chat-model.api-key = ${ANTHROPIC_API_KEY}

Configure AnthropicStreamingChatModel bean:

langchain4j.anthropic.streaming-chat-model.api-key = ${ANTHROPIC_API_KEY}

Examples