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.skillsblock to the request, - adds the required
code_executionserver tool (unless one is already configured viaserverTools(...)).
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:
thinkingTypeandthinkingBudgetTokens: 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) insideAiMessage.thinking()and whether to invokeStreamingChatResponseHandler.onPartialThinking()andTokenStream.onPartialThinking()callbacks when usingBedrockStreamingChatModel. Disabled by default. If enabled, tinking signatures will also be stored and returned inside theAiMessage.attributes().sendThinking: controls whether to send thinking and signatures stored inAiMessageto 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).
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}