Quarkus extension for integrating Anthropic Claude LLM models into Quarkus applications via LangChain4j
The quarkus-langchain4j-anthropic extension provides low-level client APIs for advanced use cases requiring direct control over API requests, beta features, or custom request handling.
The core client class that extends LangChain4j's AnthropicClient with Quarkus-specific REST client integration.
package io.quarkiverse.langchain4j.anthropic;
class QuarkusAnthropicClient extends dev.langchain4j.model.anthropic.internal.client.AnthropicClient {
/**
* Default tools beta header value
*/
public static final String BETA = "tools-2024-04-04";
/**
* Create synchronous message completion
*
* @param request The message request
* @return Complete message response
*/
public dev.langchain4j.model.anthropic.internal.api.AnthropicCreateMessageResponse
createMessage(
dev.langchain4j.model.anthropic.internal.api.AnthropicCreateMessageRequest request
);
/**
* Create streaming message completion with options
*
* @param request The message request
* @param options Streaming options (e.g., return thinking)
* @param handler Streaming response handler
*/
public void createMessage(
dev.langchain4j.model.anthropic.internal.api.AnthropicCreateMessageRequest request,
dev.langchain4j.model.anthropic.internal.client.AnthropicCreateMessageOptions options,
dev.langchain4j.model.chat.response.StreamingChatResponseHandler handler
);
/**
* Create streaming message completion without options
*
* @param request The message request
* @param handler Streaming response handler
*/
public void createMessage(
dev.langchain4j.model.anthropic.internal.api.AnthropicCreateMessageRequest request,
dev.langchain4j.model.chat.response.StreamingChatResponseHandler handler
);
/**
* Set thread-local hint to enable cURL request logging
*
* @param logCurl Whether to log cURL commands
*/
public static void setLogCurlHint(boolean logCurl);
/**
* Set thread-local hint to disable beta headers
*
* @param disableBetaHint Whether to disable beta headers
*/
public static void setDisableBetaHint(boolean disableBetaHint);
}Fluent builder for constructing QuarkusAnthropicClient instances.
class QuarkusAnthropicClient.Builder
extends dev.langchain4j.model.anthropic.internal.client.AnthropicClient.Builder<
dev.langchain4j.model.anthropic.internal.client.AnthropicClient,
QuarkusAnthropicClient.Builder
> {
/**
* Enable cURL command logging
*/
public boolean logCurl;
/**
* Disable beta headers in requests
*/
public boolean disableBetaHeader;
/**
* Build the client instance
*
* @return Configured AnthropicClient (parent class)
*/
public dev.langchain4j.model.anthropic.internal.client.AnthropicClient build();
}import io.quarkiverse.langchain4j.anthropic.QuarkusAnthropicClient;
import dev.langchain4j.model.anthropic.internal.client.AnthropicClient;
import java.time.Duration;
// Note: logCurl and disableBetaHeader are public fields, not methods
// They are set via thread-local hints before creating the client
QuarkusAnthropicClient.setLogCurlHint(true);
QuarkusAnthropicClient.setDisableBetaHint(false);
// Note: build() returns AnthropicClient (parent class), not QuarkusAnthropicClient
AnthropicClient client = QuarkusAnthropicClient.builder()
.apiKey("sk-ant-...")
.baseUrl("https://api.anthropic.com/v1/")
.version("2023-06-01")
.timeout(Duration.ofSeconds(30))
.logRequests(true)
.logResponses(true)
.build();The QuarkusAnthropicClient.Builder extends AnthropicClient.Builder from LangChain4j and inherits the following configuration methods:
/**
* Set the Anthropic API key (required)
*
* @param apiKey Your Anthropic API key (e.g., "sk-ant-...")
* @return This builder for method chaining
*/
QuarkusAnthropicClient.Builder apiKey(String apiKey);
/**
* Set the base URL for Anthropic API
*
* @param baseUrl Base URL (default: "https://api.anthropic.com/v1/")
* @return This builder for method chaining
*/
QuarkusAnthropicClient.Builder baseUrl(String baseUrl);
/**
* Set the Anthropic API version header
*
* @param version API version string (default: "2023-06-01")
* @return This builder for method chaining
*/
QuarkusAnthropicClient.Builder version(String version);
/**
* Set the beta features header value
*
* @param beta Beta header value (e.g., "tools-2024-04-04" or comma-separated list)
* @return This builder for method chaining
*/
QuarkusAnthropicClient.Builder beta(String beta);
/**
* Set request timeout duration
*
* @param timeout Timeout duration (e.g., Duration.ofSeconds(30))
* @return This builder for method chaining
*/
QuarkusAnthropicClient.Builder timeout(java.time.Duration timeout);
/**
* Enable HTTP request logging
*
* @param logRequests true to log requests, false otherwise
* @return This builder for method chaining
*/
QuarkusAnthropicClient.Builder logRequests(boolean logRequests);
/**
* Enable HTTP response logging
*
* @param logResponses true to log responses, false otherwise
* @return This builder for method chaining
*/
QuarkusAnthropicClient.Builder logResponses(boolean logResponses);Note: All inherited methods return QuarkusAnthropicClient.Builder for fluent method chaining.
JAX-RS REST client interface for direct Anthropic API access.
package io.quarkiverse.langchain4j.anthropic;
@Path("")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
interface AnthropicRestApi {
/**
* API key header name
*/
String API_KEY_HEADER = "x-api-key";
/**
* Create message completion (synchronous)
*
* @param request The message request
* @param apiMetadata HTTP headers (API key, version, beta)
* @return Complete message response
*/
@Path("/messages")
@POST
dev.langchain4j.model.anthropic.internal.api.AnthropicCreateMessageResponse
createMessage(
dev.langchain4j.model.anthropic.internal.api.AnthropicCreateMessageRequest request,
@BeanParam ApiMetadata apiMetadata
);
/**
* Stream message completion (SSE)
*
* @param request The message request
* @param apiMetadata HTTP headers (API key, version, beta)
* @return Reactive stream of message data
*/
@Path("/messages")
@POST
@RestStreamElementType(MediaType.APPLICATION_JSON)
io.smallrye.mutiny.Multi<dev.langchain4j.model.anthropic.internal.api.AnthropicStreamingData>
streamMessage(
dev.langchain4j.model.anthropic.internal.api.AnthropicCreateMessageRequest request,
@BeanParam ApiMetadata apiMetadata
);
}Immutable HTTP header metadata for API requests.
class ApiMetadata {
/**
* API key (header: x-api-key)
*/
public final String apiKey;
/**
* Anthropic API version (header: anthropic-version)
*/
public final String anthropicVersion;
/**
* Beta features (header: anthropic-beta)
* Optional, can be null
*/
public final String beta;
/**
* Create a builder
*/
public static ApiMetadata.Builder builder();
}class ApiMetadata.Builder {
/**
* Set API key
*/
public Builder apiKey(String apiKey);
/**
* Set Anthropic API version
*/
public Builder anthropicVersion(String anthropicVersion);
/**
* Set beta header value
*/
public Builder beta(String beta);
/**
* Build immutable metadata
*/
public ApiMetadata build();
}import io.quarkiverse.langchain4j.anthropic.AnthropicRestApi;
AnthropicRestApi.ApiMetadata metadata = AnthropicRestApi.ApiMetadata.builder()
.apiKey("sk-ant-...")
.anthropicVersion("2023-06-01")
.beta("tools-2024-04-04")
.build();These classes are from LangChain4j's Anthropic module:
package dev.langchain4j.model.anthropic.internal.api;
class AnthropicCreateMessageRequest {
/**
* Create a builder
*/
public static Builder builder();
/**
* Convert to builder for modification
*/
public Builder toBuilder();
/**
* Get the stream flag
*/
public Boolean isStream();
/**
* Get the messages
*/
public List<AnthropicMessage> getMessages();
/**
* Get the tools
*/
public List<AnthropicTool> getTools();
}package dev.langchain4j.model.anthropic.internal.api;
class AnthropicCreateMessageResponse {
/**
* Response ID
*/
public String id;
/**
* Response type (always "message")
*/
public String type;
/**
* Role (always "assistant")
*/
public String role;
/**
* Response content blocks
*/
public List<AnthropicMessageContent> content;
/**
* Model used
*/
public String model;
/**
* Stop reason
*/
public String stopReason;
/**
* Stop sequence (if applicable)
*/
public String stopSequence;
/**
* Token usage statistics
*/
public AnthropicUsage usage;
}package dev.langchain4j.model.anthropic.internal.api;
class AnthropicStreamingData {
/**
* Event type:
* - "message_start"
* - "content_block_start"
* - "content_block_delta"
* - "content_block_stop"
* - "message_delta"
* - "message_stop"
* - "error"
*/
public String type;
/**
* Message data (for message_start)
*/
public AnthropicMessage message;
/**
* Content block (for content_block_start)
*/
public AnthropicMessageContent contentBlock;
/**
* Delta data (for content_block_delta and message_delta)
*/
public Delta delta;
/**
* Token usage (for message_start and message_delta)
*/
public AnthropicUsage usage;
/**
* Error information (for error events)
*/
public AnthropicError error;
}package dev.langchain4j.model.anthropic.internal.api;
class AnthropicMessage {
/**
* Create a builder
*/
public static Builder builder();
/**
* Get the role
*/
public AnthropicRole getRole();
/**
* Get the content
*/
public List<AnthropicMessageContent> getContent();
}class AnthropicMessage.Builder {
/**
* Set the role (USER or ASSISTANT)
*/
public Builder role(AnthropicRole role);
/**
* Set the content (list of content blocks)
*/
public Builder content(List<AnthropicMessageContent> content);
/**
* Build the message
*/
public AnthropicMessage build();
}package dev.langchain4j.model.anthropic.internal.api;
enum AnthropicRole {
/**
* User role for user messages
*/
USER,
/**
* Assistant role for Claude responses
*/
ASSISTANT
}package dev.langchain4j.model.anthropic.internal.api;
class AnthropicTextContent extends AnthropicMessageContent {
/**
* Create a builder
*/
public static Builder builder();
/**
* Get the text content
*/
public String getText();
}class AnthropicTextContent.Builder {
/**
* Set the text content
*/
public Builder text(String text);
/**
* Build the text content
*/
public AnthropicTextContent build();
}Note: The Quarkus extension automatically creates and configures AnthropicChatModel instances via CDI. Direct client creation is typically only needed for advanced use cases where you need to bypass CDI or make direct API calls.
import io.quarkiverse.langchain4j.anthropic.QuarkusAnthropicClient;
import dev.langchain4j.model.anthropic.internal.client.AnthropicClient;
import java.time.Duration;
// Create custom client for direct API calls
// Note: build() returns AnthropicClient, not QuarkusAnthropicClient
AnthropicClient client = QuarkusAnthropicClient.builder()
.apiKey("sk-ant-...")
.baseUrl("https://api.anthropic.com/v1/")
.timeout(Duration.ofSeconds(60))
.build();
// Use the client to make direct API calls (see examples below)import io.quarkiverse.langchain4j.anthropic.QuarkusAnthropicClient;
import dev.langchain4j.model.anthropic.internal.client.AnthropicClient;
import dev.langchain4j.model.anthropic.internal.api.*;
import java.util.List;
AnthropicClient client = QuarkusAnthropicClient.builder()
.apiKey("sk-ant-...")
.build();
// Build request with message content
AnthropicCreateMessageRequest request = AnthropicCreateMessageRequest.builder()
.model("claude-opus-4-20250514")
.maxTokens(1024)
.messages(List.of(
AnthropicMessage.builder()
.role(dev.langchain4j.model.anthropic.internal.api.AnthropicRole.USER)
.content(List.of(
AnthropicTextContent.builder()
.text("Hello, Claude!")
.build()
))
.build()
))
.build();
// Make synchronous call
AnthropicCreateMessageResponse response = client.createMessage(request);
System.out.println(response.content.get(0).text);import io.quarkiverse.langchain4j.anthropic.QuarkusAnthropicClient;
import dev.langchain4j.model.anthropic.internal.client.AnthropicClient;
import dev.langchain4j.model.anthropic.internal.api.*;
import dev.langchain4j.model.anthropic.internal.client.AnthropicCreateMessageOptions;
import dev.langchain4j.model.chat.response.*;
import java.util.List;
AnthropicClient client = QuarkusAnthropicClient.builder()
.apiKey("sk-ant-...")
.build();
AnthropicCreateMessageRequest request = AnthropicCreateMessageRequest.builder()
.model("claude-opus-4-20250514")
.maxTokens(1024)
.messages(List.of(
AnthropicMessage.builder()
.role(dev.langchain4j.model.anthropic.internal.api.AnthropicRole.USER)
.content(List.of(
AnthropicTextContent.builder()
.text("Count to 10")
.build()
))
.build()
))
.build();
// Enable thinking in streaming
AnthropicCreateMessageOptions options = new AnthropicCreateMessageOptions(true);
client.createMessage(request, options, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(PartialResponse response,
PartialResponseContext context) {
System.out.print(response.text());
}
@Override
public void onCompleteResponse(ChatResponse response) {
System.out.println("\nComplete!");
}
@Override
public void onError(Throwable error) {
error.printStackTrace();
}
});Beta headers are automatically managed but can be controlled:
import io.quarkiverse.langchain4j.anthropic.QuarkusAnthropicClient;
import dev.langchain4j.model.anthropic.internal.client.AnthropicClient;
// Disable beta headers using thread-local hint
QuarkusAnthropicClient.setDisableBetaHint(true);
AnthropicClient client = QuarkusAnthropicClient.builder()
.apiKey("sk-ant-...")
.build();
// Or set custom beta header via builder method
AnthropicClient client = QuarkusAnthropicClient.builder()
.apiKey("sk-ant-...")
.beta("tools-2024-04-04,prompt-caching-2024-07-31")
.build();For fine-grained control per request:
// Disable beta headers for next request only
QuarkusAnthropicClient.setDisableBetaHint(true);
response = client.createMessage(request);
// Beta headers are re-enabled for subsequent requestsimport io.quarkiverse.langchain4j.anthropic.QuarkusAnthropicClient;
import dev.langchain4j.model.anthropic.internal.client.AnthropicClient;
// Via thread-local hint before creating client
QuarkusAnthropicClient.setLogCurlHint(true);
AnthropicClient client = QuarkusAnthropicClient.builder()
.apiKey("sk-ant-...")
.build();
// For single request, set hint before the call
QuarkusAnthropicClient.setLogCurlHint(true);
client.createMessage(request);The AnthropicClientLogger class provides custom HTTP logging functionality.
package io.quarkiverse.langchain4j.anthropic;
class QuarkusAnthropicClient.AnthropicClientLogger
implements org.jboss.resteasy.reactive.client.api.ClientLogger {
/**
* Constructor
*
* @param logRequests Whether to log requests
* @param logResponses Whether to log responses
*/
public AnthropicClientLogger(boolean logRequests, boolean logResponses);
/**
* Constructor with cURL support
*
* @param logRequests Whether to log requests
* @param logResponses Whether to log responses
* @param logCurl Whether to log as cURL commands
*/
public AnthropicClientLogger(boolean logRequests, boolean logResponses, boolean logCurl);
}The logger implementation handles HTTP request and response logging at INFO level, automatically masking sensitive headers like API keys. When cURL logging is enabled, it generates executable cURL commands for debugging purposes.
## REST Client Factory
For even more control, create a custom REST client:
```java
import io.quarkiverse.langchain4j.anthropic.AnthropicRestApi;
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
import java.net.URI;
import java.util.concurrent.TimeUnit;
AnthropicRestApi restApi = QuarkusRestClientBuilder.newBuilder()
.baseUri(URI.create("https://api.anthropic.com/v1/"))
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build(AnthropicRestApi.class);
// Use REST API directly
AnthropicRestApi.ApiMetadata metadata = AnthropicRestApi.ApiMetadata.builder()
.apiKey("sk-ant-...")
.anthropicVersion("2023-06-01")
.build();
AnthropicCreateMessageResponse response = restApi.createMessage(request, metadata);The Quarkus extension automatically creates AnthropicChatModel instances via CDI with configuration from application.properties. For most use cases, you should use CDI injection rather than creating models manually.
If you need to create an AnthropicChatModel programmatically, configure it using the builder's configuration methods (the builder does not accept a custom client instance):
import dev.langchain4j.model.anthropic.AnthropicChatModel;
// Create chat model with configuration
// Note: This bypasses the Quarkus extension's automatic configuration
AnthropicChatModel chatModel = AnthropicChatModel.builder()
.apiKey("sk-ant-...")
.baseUrl("https://api.anthropic.com/v1/")
.version("2023-06-01")
.modelName("claude-opus-4-20250514")
.maxTokens(2048)
.temperature(0.7)
.topP(1.0)
.topK(40)
.cacheSystemMessages(true)
.thinking(thinking -> thinking
.type("enabled")
.budgetTokens(10000)
.returnThinking(true)
)
.build();For CDI-managed models with automatic configuration, use the injection patterns documented in CDI Injection.
The client throws exceptions for error cases:
try {
AnthropicCreateMessageResponse response = client.createMessage(request);
} catch (jakarta.ws.rs.WebApplicationException e) {
// HTTP error (4xx, 5xx)
int status = e.getResponse().getStatus();
String body = e.getResponse().readEntity(String.class);
System.err.println("HTTP " + status + ": " + body);
} catch (Exception e) {
// Network error, timeout, etc.
e.printStackTrace();
}Common error codes:
AnthropicCreateMessageRequest request = AnthropicCreateMessageRequest.builder()
.model("claude-opus-4-20250514")
.maxTokens(2048)
.system("You are a helpful coding assistant.")
.messages(List.of(
AnthropicMessage.builder()
.role(dev.langchain4j.model.anthropic.internal.api.AnthropicRole.USER)
.content(List.of(
AnthropicTextContent.builder()
.text("Explain recursion")
.build()
))
.build()
))
.build();AnthropicCreateMessageRequest request = AnthropicCreateMessageRequest.builder()
.model("claude-opus-4-20250514")
.maxTokens(4096)
.thinking(AnthropicThinking.builder()
.type("enabled")
.budgetTokens(10000)
.build())
.messages(List.of(
AnthropicMessage.builder()
.role(dev.langchain4j.model.anthropic.internal.api.AnthropicRole.USER)
.content(List.of(
AnthropicTextContent.builder()
.text("Solve this complex problem: ...")
.build()
))
.build()
))
.build();AnthropicCreateMessageRequest request = AnthropicCreateMessageRequest.builder()
.model("claude-opus-4-20250514")
.maxTokens(2048)
.tools(List.of(
AnthropicTool.builder()
.name("get_weather")
.description("Get current weather for a location")
.inputSchema(Map.of(
"type", "object",
"properties", Map.of(
"location", Map.of("type", "string")
),
"required", List.of("location")
))
.build()
))
.messages(List.of(
AnthropicMessage.builder()
.role(dev.langchain4j.model.anthropic.internal.api.AnthropicRole.USER)
.content(List.of(
AnthropicTextContent.builder()
.text("What's the weather in Paris?")
.build()
))
.build()
))
.build();Use the low-level client API when you need:
For most use cases, prefer the high-level CDI injection pattern for better integration with Quarkus features and simpler code.
Install with Tessl CLI
npx tessl i tessl/maven-io-quarkiverse-langchain4j--quarkus-langchain4j-anthropic@1.7.0