CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-dev-langchain4j--langchain4j

Build LLM-powered applications in Java with support for chatbots, agents, RAG, tools, and much more

Overview
Eval results
Files

messages.mddocs/

Chat Messages

Message types for chat interactions with language models. LangChain4j supports various message types including user messages, AI messages, system messages, tool execution results, and multimodal content.

Capabilities

ChatMessage Interface

Base interface for all chat message types.

package dev.langchain4j.data.message;

/**
 * Base interface for all chat messages
 */
public interface ChatMessage {
    /**
     * Get the type of this message
     * @return Message type
     */
    ChatMessageType type();
}

Thread Safety: The ChatMessage interface itself does not mandate thread safety. Implementations vary - check specific message type documentation.

Common Pitfalls:

  • Do NOT assume all implementations are immutable
  • Do NOT cast to concrete types without instanceof checks
  • Do NOT rely on equals() without understanding implementation specifics

Edge Cases:

  • Custom implementations may exist beyond the standard types
  • Type-checking should use type() method, not instanceof when possible

Performance Notes:

  • Interface method calls are virtual - minimal overhead
  • Type checking via type() is typically faster than instanceof

Exception Handling:

  • type() should never throw exceptions in standard implementations
  • Custom implementations should document any exceptions thrown

Related APIs:

  • ChatMessageType - enum for message type identification
  • UserMessage, AiMessage, SystemMessage, ToolExecutionResultMessage - concrete implementations

ChatMessageType Enum

package dev.langchain4j.data.message;

/**
 * Enum representing the type of chat message
 */
public enum ChatMessageType {
    SYSTEM,
    USER,
    AI,
    TOOL_EXECUTION_RESULT,
    CUSTOM
}

Thread Safety: Enums are inherently thread-safe and immutable.

Common Pitfalls:

  • Do NOT use == for comparison in generic code - use equals() for consistency
  • Do NOT assume ordinal() values are stable across library versions
  • Do NOT serialize via ordinal() - use name() instead

Edge Cases:

  • CUSTOM type is for extension scenarios - not used by core library
  • Some LLM providers may not support all message types

Performance Notes:

  • Enum comparisons via == are extremely fast (reference equality)
  • No object allocation on access

Exception Handling:

  • Enum valueOf() throws IllegalArgumentException for invalid names
  • Always validate string inputs before calling valueOf()

Related APIs:

  • ChatMessage.type() - returns this enum value
  • All message implementations use this enum

UserMessage

Represents a message from a user. Supports both simple text and multimodal content (text, images, audio, video, PDF).

package dev.langchain4j.data.message;

import java.util.List;
import java.util.Map;

/**
 * Represents a message from a user
 * Supports multimodal content (text, image, audio, video, PDF)
 */
public class UserMessage implements ChatMessage {
    /**
     * Create a UserMessage from text
     * @param text Message text
     */
    public UserMessage(String text);

    /**
     * Create a UserMessage with name and text
     * @param name User name
     * @param text Message text
     */
    public UserMessage(String name, String text);

    /**
     * Create a UserMessage from content(s)
     * @param contents Message contents
     */
    public UserMessage(Content... contents);

    /**
     * Create a UserMessage with name and content(s)
     * @param name User name
     * @param contents Message contents
     */
    public UserMessage(String name, Content... contents);

    /**
     * Create a UserMessage from list of contents
     * @param contents List of message contents
     */
    public UserMessage(List<Content> contents);

    /**
     * Create a UserMessage with name and list of contents
     * @param name User name
     * @param contents List of message contents
     */
    public UserMessage(String name, List<Content> contents);

    /**
     * Get the user name
     * @return User name or null if not set
     */
    public String name();

    /**
     * Get message contents
     * @return List of contents
     */
    public List<Content> contents();

    /**
     * Get single text content
     * Only use if message contains exactly one TextContent
     * @return Text string
     * @throws RuntimeException if not single text content
     */
    public String singleText();

    /**
     * Check if message has single text content
     * @return true if message contains exactly one TextContent
     */
    public boolean hasSingleText();

    /**
     * Get custom attributes (not sent to model, stored in memory)
     * @return Mutable map of attributes
     */
    public Map<String, Object> attributes();

    /**
     * Get message type
     * @return USER message type
     */
    public ChatMessageType type();

    /**
     * Create UserMessage from text (factory method)
     * @param text Message text
     * @return UserMessage instance
     */
    public static UserMessage from(String text);

    /**
     * Create UserMessage from text (factory method)
     * @param text Message text
     * @return UserMessage instance
     */
    public static UserMessage userMessage(String text);

    /**
     * Create builder
     * @return Builder instance
     */
    public static Builder builder();
}

Thread Safety: UserMessage instances are NOT fully immutable. The attributes() map is mutable and shared, making concurrent access unsafe without external synchronization. Contents list is effectively immutable after construction. Reading name/contents is thread-safe, but mutating attributes requires synchronization.

Common Pitfalls:

  • Do NOT call singleText() without checking hasSingleText() first - throws RuntimeException
  • Do NOT assume name() is non-null - it's optional and can return null
  • Do NOT modify the attributes() map from multiple threads without synchronization
  • Do NOT pass null text to constructors - use empty string instead if needed
  • Do NOT assume all content types are supported by all LLM providers
  • Do NOT pass empty contents list - creates invalid message state

Edge Cases:

  • Empty text string is valid but may cause issues with some LLM providers
  • Null name is valid and common (most messages don't have names)
  • Multiple TextContent items in contents list is valid but unusual
  • Mixing text constructor with subsequent content manipulation via builder is error-prone
  • Image URLs may have size/format restrictions depending on provider
  • Base64-encoded images in data URLs can exceed provider token limits

Performance Notes:

  • Constructor creates defensive copies of content lists - O(n) allocation
  • singleText() performs type checking and extraction - not cached
  • attributes() returns mutable map reference - no copying
  • Factory methods (from(), userMessage()) have same cost as constructors
  • Builder pattern adds minimal overhead but improves readability for complex messages
  • Large base64 images in content significantly increase serialization time

Exception Handling:

  • singleText() throws RuntimeException if message doesn't have exactly one TextContent
  • Constructors throw NullPointerException if text parameter is null (in text-only constructors)
  • Constructors throw NullPointerException if contents parameter is null
  • Builder's build() may throw IllegalStateException if required fields not set

Related APIs:

  • Content - base interface for message content
  • TextContent - text content type
  • ImageContent - image content with detail levels
  • AudioContent, VideoContent, PdfFileContent - other multimodal types
  • ChatMessage - base interface

Usage Examples:

import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.TextContent;
import dev.langchain4j.data.message.ImageContent;

// Simple text message
UserMessage msg1 = UserMessage.from("Hello, how are you?");
UserMessage msg2 = new UserMessage("What is the capital of France?");

// Message with user name
UserMessage msg3 = new UserMessage("Alice", "I need help");

// Check before accessing single text
if (msg1.hasSingleText()) {
    String text = msg1.singleText();
    System.out.println(text);
}

// Multimodal message with text and image
UserMessage msg4 = new UserMessage(
    TextContent.from("What's in this image?"),
    ImageContent.from("https://example.com/image.jpg")
);

// High detail image for detailed analysis
UserMessage msg5 = new UserMessage(
    TextContent.from("Analyze this chart in detail"),
    ImageContent.from("https://example.com/chart.png", ImageContent.DetailLevel.HIGH)
);

// Multiple images with text
UserMessage msg6 = new UserMessage(
    TextContent.from("Compare these two images"),
    ImageContent.from("https://example.com/before.jpg"),
    ImageContent.from("https://example.com/after.jpg")
);

// Base64 encoded image
UserMessage msg7 = new UserMessage(
    TextContent.from("What's in this image?"),
    ImageContent.from("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...")
);

// Using builder for complex cases
UserMessage msg8 = UserMessage.builder()
    .name("Bob")
    .contents(
        TextContent.from("Analyze this"),
        ImageContent.from("data:image/png;base64,...")
    )
    .build();

// Using attributes for metadata (not sent to LLM)
UserMessage msg9 = UserMessage.from("Hello");
msg9.attributes().put("userId", "12345");
msg9.attributes().put("sessionId", "abc-def");
msg9.attributes().put("timestamp", System.currentTimeMillis());

AiMessage

Represents a message generated by AI. Can contain text, thinking/reasoning content, tool execution requests, and generated images.

package dev.langchain4j.data.message;

import dev.langchain4j.agent.tool.ToolExecutionRequest;
import dev.langchain4j.data.image.Image;
import java.util.List;
import java.util.Map;

/**
 * Represents a message generated by AI
 * Can contain text, thinking, tool execution requests, and attributes
 */
public class AiMessage implements ChatMessage {
    /**
     * Create AiMessage with text
     * @param text Message text
     */
    public AiMessage(String text);

    /**
     * Create AiMessage with tool execution requests
     * @param toolExecutionRequests Tool execution requests
     */
    public AiMessage(List<ToolExecutionRequest> toolExecutionRequests);

    /**
     * Create AiMessage with text and tool execution requests
     * @param text Message text
     * @param toolExecutionRequests Tool execution requests
     */
    public AiMessage(String text, List<ToolExecutionRequest> toolExecutionRequests);

    /**
     * Get message text
     * @return Message text or null
     */
    public String text();

    /**
     * Get thinking/reasoning text
     * @return Thinking text or null
     */
    public String thinking();

    /**
     * Get tool execution requests
     * @return List of tool execution requests
     */
    public List<ToolExecutionRequest> toolExecutionRequests();

    /**
     * Check if message has tool execution requests
     * @return true if has tool execution requests
     */
    public boolean hasToolExecutionRequests();

    /**
     * Get generated images (currently works for Gemini only)
     * @return List of generated images
     */
    public List<Image> images();

    /**
     * Get custom attributes (provider-specific data)
     * @return Map of attributes
     */
    public Map<String, Object> attributes();

    /**
     * Create a clone with different text
     * @param text New text
     * @return New AiMessage with updated text
     */
    public AiMessage withText(String text);

    /**
     * Get message type
     * @return AI message type
     */
    public ChatMessageType type();

    /**
     * Create AiMessage from text (factory method)
     * @param text Message text
     * @return AiMessage instance
     */
    public static AiMessage from(String text);

    /**
     * Create AiMessage from tool execution requests (factory method)
     * @param toolExecutionRequests Tool execution requests
     * @return AiMessage instance
     */
    public static AiMessage from(List<ToolExecutionRequest> toolExecutionRequests);

    /**
     * Create AiMessage from text (factory method)
     * @param text Message text
     * @return AiMessage instance
     */
    public static AiMessage aiMessage(String text);

    /**
     * Create AiMessage from tool execution requests (factory method)
     * @param toolExecutionRequests Tool execution requests
     * @return AiMessage instance
     */
    public static AiMessage aiMessage(List<ToolExecutionRequest> toolExecutionRequests);

    /**
     * Create builder
     * @return Builder instance
     */
    public static Builder builder();
}

Thread Safety: AiMessage instances are NOT fully immutable. The attributes() map is mutable and shared across threads. Text, thinking, and toolExecutionRequests are effectively immutable after construction, but attributes require external synchronization for concurrent access.

Common Pitfalls:

  • Do NOT assume text() is non-null - can be null when only tool execution requests present
  • Do NOT assume thinking() is populated - only some models (Claude with thinking enabled) provide this
  • Do NOT assume toolExecutionRequests() is null when empty - returns empty list, not null
  • Do NOT forget to check hasToolExecutionRequests() before processing tools
  • Do NOT assume images() is supported - only Gemini models currently populate this
  • Do NOT modify the returned lists directly - they may be unmodifiable
  • Do NOT use withText() for mutations in-place - it returns a NEW instance

Edge Cases:

  • AiMessage with both text and tool execution requests is valid (parallel tool calls with explanation)
  • AiMessage with neither text nor tool execution requests is technically possible but rare
  • Empty string text is different from null text - null means no text field was set
  • Thinking content is separate from main text and not all providers expose it
  • Generated images list is provider-specific and usually empty
  • Attributes may contain provider-specific data like token usage, finish reason, etc.

Performance Notes:

  • withText() creates a new AiMessage instance - O(1) if using builder pattern internally
  • hasToolExecutionRequests() is O(1) check (not list traversal)
  • Accessing attributes() returns direct map reference - no copying
  • Tool execution requests list is created once at construction - no reallocation

Exception Handling:

  • Constructors throw NullPointerException if required parameters are null
  • withText() throws NullPointerException if text parameter is null
  • Builder's build() may throw IllegalStateException if in invalid state
  • Accessing list elements may throw IndexOutOfBoundsException

Related APIs:

  • ToolExecutionRequest - represents a tool call request from AI
  • ToolExecutionResultMessage - response to tool execution
  • Image - generated image data (Gemini-specific)
  • ChatMessage - base interface

Usage Examples:

import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.agent.tool.ToolExecutionRequest;

// Simple text response
AiMessage msg1 = AiMessage.from("The capital of France is Paris.");
AiMessage msg2 = new AiMessage("Hello! How can I help you?");

// Check for tool execution requests
AiMessage msg3 = getResponseFromModel();
if (msg3.hasToolExecutionRequests()) {
    for (ToolExecutionRequest request : msg3.toolExecutionRequests()) {
        // Execute tool and send result back
        System.out.println("Tool: " + request.name());
        System.out.println("Arguments: " + request.arguments());
    }
}

// Message with both text and tool requests
AiMessage msg4 = new AiMessage(
    "I'll check the weather for you.",
    List.of(
        ToolExecutionRequest.builder()
            .name("getWeather")
            .arguments("{\"location\": \"Paris\"}")
            .build()
    )
);

// Accessing thinking/reasoning (Claude models with extended thinking)
AiMessage msg5 = getResponseFromClaude();
if (msg5.thinking() != null) {
    System.out.println("AI reasoning: " + msg5.thinking());
}
System.out.println("AI response: " + msg5.text());

// Modifying text while preserving other fields
AiMessage original = AiMessage.from("Original text");
AiMessage modified = original.withText("Modified text");
// original is unchanged, modified is new instance

// Accessing provider-specific attributes
AiMessage msg6 = getResponseFromModel();
Map<String, Object> attrs = msg6.attributes();
Integer tokenCount = (Integer) attrs.get("tokenUsage");
String finishReason = (String) attrs.get("finishReason");

// Gemini: accessing generated images
AiMessage geminiResponse = getResponseFromGemini();
List<Image> generatedImages = geminiResponse.images();
if (!generatedImages.isEmpty()) {
    for (Image img : generatedImages) {
        System.out.println("Generated image: " + img.url());
    }
}

// Using builder for complex construction
AiMessage msg7 = AiMessage.builder()
    .text("Here are the results")
    .toolExecutionRequests(
        ToolExecutionRequest.builder()
            .id("call_123")
            .name("searchDatabase")
            .arguments("{\"query\": \"users\"}")
            .build()
    )
    .build();

SystemMessage

Represents a system message that provides instructions or context to the AI.

package dev.langchain4j.data.message;

import java.util.List;
import java.util.Optional;

/**
 * Represents a system message with instructions for the AI
 * Typically defined by a developer to control AI behavior
 */
public class SystemMessage implements ChatMessage {
    /**
     * Create a SystemMessage
     * @param text System message text
     */
    public SystemMessage(String text);

    /**
     * Get message text
     * @return System message text
     */
    public String text();

    /**
     * Get message type
     * @return SYSTEM message type
     */
    public ChatMessageType type();

    /**
     * Create SystemMessage from text (factory method)
     * @param text Message text
     * @return SystemMessage instance
     */
    public static SystemMessage from(String text);

    /**
     * Create SystemMessage from text (factory method)
     * @param text Message text
     * @return SystemMessage instance
     */
    public static SystemMessage systemMessage(String text);

    /**
     * Find first system message in list
     * @param messages List of messages
     * @return Optional containing first system message if found
     */
    public static Optional<SystemMessage> findFirst(List<ChatMessage> messages);

    /**
     * Find last system message in list
     * @param messages List of messages
     * @return Optional containing last system message if found
     */
    public static Optional<SystemMessage> findLast(List<ChatMessage> messages);

    /**
     * Find all system messages in list
     * @param messages List of messages
     * @return List of all system messages
     */
    public static List<SystemMessage> findAll(List<ChatMessage> messages);
}

Thread Safety: SystemMessage is immutable and thread-safe. Once created, the text cannot be changed. Multiple threads can safely read from the same instance.

Common Pitfalls:

  • Do NOT use multiple system messages unless your LLM provider explicitly supports it (most don't)
  • Do NOT place system messages in the middle of conversation - most providers expect them at the start
  • Do NOT use extremely long system messages - they consume tokens from your context window
  • Do NOT assume system message behavior is consistent across providers
  • Do NOT forget that system messages are sent with EVERY request in a conversation
  • Do NOT confuse system messages with user messages - they serve different purposes

Edge Cases:

  • Some providers (Anthropic) only support one system message at the beginning
  • Some providers (OpenAI) allow multiple system messages at any position
  • Empty system message text is technically valid but pointless
  • Very long system messages may hit provider-specific length limits
  • System message placement affects token counting and context window usage
  • Some providers merge multiple system messages, others reject them

Performance Notes:

  • Immutable - safe for caching and reuse across conversations
  • No allocation overhead after construction
  • Static finder methods iterate list - O(n) complexity
  • findFirst() and findLast() return empty Optional if not found - no exception overhead

Exception Handling:

  • Constructor throws NullPointerException if text is null
  • Static finder methods throw NullPointerException if messages list is null
  • Static finders return empty Optional (not null) when no matches found
  • No runtime exceptions during normal usage after construction

Related APIs:

  • ChatMessage - base interface
  • UserMessage - user input messages
  • AiMessage - AI response messages

Usage Examples:

import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.ChatMessage;
import java.util.List;
import java.util.Optional;

// Simple system message
SystemMessage sys1 = SystemMessage.from("You are a helpful assistant.");
SystemMessage sys2 = new SystemMessage("You are a Python expert. Answer concisely.");

// Complex system instructions
SystemMessage sys3 = SystemMessage.from(
    "You are a code review assistant. " +
    "Analyze code for: security issues, performance problems, " +
    "style violations, and potential bugs. " +
    "Provide specific line numbers and suggestions."
);

// System message with output formatting instructions
SystemMessage sys4 = SystemMessage.from(
    "You are a JSON API. Always respond with valid JSON. " +
    "Never include explanatory text outside the JSON structure."
);

// Finding system messages in conversation history
List<ChatMessage> conversation = List.of(
    SystemMessage.from("You are a helpful assistant."),
    UserMessage.from("Hello"),
    AiMessage.from("Hi there!"),
    UserMessage.from("What's the weather?")
);

// Find first system message
Optional<SystemMessage> first = SystemMessage.findFirst(conversation);
first.ifPresent(msg -> System.out.println("System prompt: " + msg.text()));

// Find last system message (useful if system context changes)
Optional<SystemMessage> last = SystemMessage.findLast(conversation);

// Find all system messages
List<SystemMessage> all = SystemMessage.findAll(conversation);
System.out.println("Found " + all.size() + " system messages");

// Safe usage pattern
if (first.isPresent()) {
    String systemPrompt = first.get().text();
    // Use system prompt
} else {
    // No system message defined, use default
    systemPrompt = "You are a helpful assistant.";
}

// Reusing system message across conversations (safe - immutable)
SystemMessage sharedSystemMessage = SystemMessage.from("You are an expert.");
List<ChatMessage> conv1 = List.of(sharedSystemMessage, UserMessage.from("Question 1"));
List<ChatMessage> conv2 = List.of(sharedSystemMessage, UserMessage.from("Question 2"));

ToolExecutionResultMessage

Represents the result of a tool execution in response to a tool execution request from an AI message.

package dev.langchain4j.data.message;

import dev.langchain4j.agent.tool.ToolExecutionRequest;

/**
 * Represents the result of a tool execution
 * Sent in response to a ToolExecutionRequest from AiMessage
 */
public class ToolExecutionResultMessage implements ChatMessage {
    /**
     * Create a ToolExecutionResultMessage
     * @param id Tool execution ID
     * @param toolName Tool name
     * @param text Execution result text
     */
    public ToolExecutionResultMessage(String id, String toolName, String text);

    /**
     * Get tool execution ID
     * @return Execution ID
     */
    public String id();

    /**
     * Get tool name
     * @return Tool name
     */
    public String toolName();

    /**
     * Get execution result text
     * @return Result text
     */
    public String text();

    /**
     * Check if execution resulted in error
     * @return true if error, false if success, null if unknown
     */
    public Boolean isError();

    /**
     * Get message type
     * @return TOOL_EXECUTION_RESULT message type
     */
    public ChatMessageType type();

    /**
     * Create from ToolExecutionRequest and result
     * @param request Tool execution request
     * @param toolExecutionResult Execution result
     * @return ToolExecutionResultMessage instance
     */
    public static ToolExecutionResultMessage from(
        ToolExecutionRequest request,
        String toolExecutionResult
    );

    /**
     * Create from ID, name, and result
     * @param id Tool execution ID
     * @param toolName Tool name
     * @param toolExecutionResult Execution result
     * @return ToolExecutionResultMessage instance
     */
    public static ToolExecutionResultMessage from(
        String id,
        String toolName,
        String toolExecutionResult
    );

    /**
     * Create builder
     * @return Builder instance
     */
    public static Builder builder();
}

Thread Safety: ToolExecutionResultMessage is immutable and thread-safe after construction. All fields are final and cannot be modified.

Common Pitfalls:

  • Do NOT mismatch ID between ToolExecutionRequest and result - must be exact match
  • Do NOT forget to include the tool execution ID - providers use it to correlate requests/results
  • Do NOT send result without prior tool execution request in conversation
  • Do NOT assume isError() is always set - can be null (unknown state)
  • Do NOT send extremely large result text - may exceed token limits
  • Do NOT send binary data directly - encode as base64 or URL first
  • Do NOT forget to handle tool execution exceptions - should be reported in result text

Edge Cases:

  • Multiple tool executions may be in flight - ID matching is critical
  • Tool execution may time out - need to decide on error vs empty result
  • Tool may return binary data - needs encoding to string
  • Tool may return structured data - consider JSON serialization
  • Error state (isError) may be null, true, or false - handle all three cases
  • Empty result text is valid for void-returning tools
  • Very long result text may be truncated by provider

Performance Notes:

  • Immutable - safe for caching and concurrent access
  • Factory method from(ToolExecutionRequest, String) is preferred over constructor
  • No allocation overhead beyond the message itself
  • Result text is stored as-is - no parsing or validation performed

Exception Handling:

  • Constructor throws NullPointerException if id, toolName, or text is null
  • Factory methods throw NullPointerException if request or result is null
  • Builder's build() throws IllegalStateException if required fields not set
  • No exceptions during normal field access

Related APIs:

  • ToolExecutionRequest - the request this result responds to
  • AiMessage.toolExecutionRequests() - source of tool requests
  • ChatMessage - base interface

Usage Examples:

import dev.langchain4j.data.message.ToolExecutionResultMessage;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.agent.tool.ToolExecutionRequest;

// Get tool execution request from AI message
AiMessage aiResponse = chatModel.generate(messages);
if (aiResponse.hasToolExecutionRequests()) {
    for (ToolExecutionRequest request : aiResponse.toolExecutionRequests()) {

        // Execute the tool
        String result;
        try {
            result = executeTool(request.name(), request.arguments());

            // Create result message - preferred method
            ToolExecutionResultMessage resultMsg =
                ToolExecutionResultMessage.from(request, result);

        } catch (Exception e) {
            // Handle tool execution error
            result = "Error: " + e.getMessage();

            // Create error result with builder
            ToolExecutionResultMessage errorMsg =
                ToolExecutionResultMessage.builder()
                    .id(request.id())
                    .toolName(request.name())
                    .text(result)
                    .isError(true)
                    .build();
        }
    }
}

// Manual construction (less common)
ToolExecutionResultMessage msg1 = new ToolExecutionResultMessage(
    "call_123",
    "getWeather",
    "Temperature: 72°F, Condition: Sunny"
);

// Factory method with explicit ID and name
ToolExecutionResultMessage msg2 = ToolExecutionResultMessage.from(
    "call_456",
    "searchDatabase",
    "[{\"id\": 1, \"name\": \"John\"}, {\"id\": 2, \"name\": \"Jane\"}]"
);

// Handling tool errors explicitly
ToolExecutionResultMessage errorMsg = ToolExecutionResultMessage.builder()
    .id("call_789")
    .toolName("invalidTool")
    .text("Tool not found: invalidTool")
    .isError(true)
    .build();

// Complete tool execution flow
List<ChatMessage> conversation = new ArrayList<>();
conversation.add(SystemMessage.from("You are a helpful assistant."));
conversation.add(UserMessage.from("What's the weather in Paris?"));

// Get AI response with tool request
AiMessage aiMsg = chatModel.generate(conversation);
conversation.add(aiMsg);

// Execute tools and add results
if (aiMsg.hasToolExecutionRequests()) {
    for (ToolExecutionRequest req : aiMsg.toolExecutionRequests()) {
        String result = toolExecutor.execute(req.name(), req.arguments());
        ToolExecutionResultMessage resultMsg =
            ToolExecutionResultMessage.from(req, result);
        conversation.add(resultMsg);
    }

    // Get final AI response with tool results
    AiMessage finalResponse = chatModel.generate(conversation);
    System.out.println(finalResponse.text());
}

// Checking error status
ToolExecutionResultMessage result = getToolResult();
Boolean isError = result.isError();
if (isError == null) {
    // Error status unknown
    System.out.println("Tool execution status unknown");
} else if (isError) {
    // Explicit error
    System.err.println("Tool failed: " + result.text());
} else {
    // Success
    System.out.println("Tool succeeded: " + result.text());
}

Content Types

Content Interface

Base interface for message content in multimodal messages.

package dev.langchain4j.data.message;

/**
 * Base interface for message content
 */
public interface Content {
    /**
     * Get content type
     * @return Content type
     */
    ContentType type();
}

Thread Safety: Interface is thread-safe. Implementations vary - check specific content type documentation.

Common Pitfalls:

  • Do NOT cast to concrete types without instanceof checks
  • Do NOT assume all content types are supported by all providers
  • Do NOT mix content types inappropriately (e.g., audio in non-audio-supporting providers)

Edge Cases:

  • Custom content implementations possible but not standard
  • Type checking should use type() method when possible

Performance Notes:

  • Interface method call overhead is minimal
  • Type checking via type() is efficient

Exception Handling:

  • type() should not throw exceptions in standard implementations

Related APIs:

  • ContentType - enum for content type identification
  • TextContent, ImageContent, AudioContent, VideoContent, PdfFileContent - implementations

ContentType Enum

package dev.langchain4j.data.message;

/**
 * Enum representing content type
 */
public enum ContentType {
    TEXT,
    IMAGE,
    AUDIO,
    VIDEO,
    PDF_FILE
}

Thread Safety: Enums are inherently thread-safe and immutable.

Common Pitfalls:

  • Do NOT assume all providers support all content types
  • Do NOT use ordinal() for serialization - use name() instead
  • Do NOT use == in generic code - prefer equals() for consistency

Edge Cases:

  • TEXT is universally supported
  • IMAGE is widely supported (GPT-4V, Claude 3+, Gemini Pro Vision)
  • AUDIO, VIDEO, PDF_FILE have limited provider support
  • Check provider documentation for content type support

Performance Notes:

  • Enum comparison via == is O(1) reference equality
  • No allocation overhead

Exception Handling:

  • valueOf() throws IllegalArgumentException for invalid names

Related APIs:

  • Content.type() - returns this enum value
  • All content implementations use this enum

TextContent

Text content for multimodal messages.

package dev.langchain4j.data.message;

/**
 * Represents text content
 */
public class TextContent implements Content {
    /**
     * Create text content
     * @param text Text string
     */
    public TextContent(String text);

    /**
     * Get text
     * @return Text string
     */
    public String text();

    /**
     * Get content type
     * @return TEXT content type
     */
    public ContentType type();

    /**
     * Create text content (factory method)
     * @param text Text string
     * @return TextContent instance
     */
    public static TextContent from(String text);
}

Thread Safety: TextContent is immutable and thread-safe. Text string cannot be modified after construction.

Common Pitfalls:

  • Do NOT pass null text - throws NullPointerException
  • Do NOT assume empty string is invalid - it's allowed but may cause issues
  • Do NOT forget that text is stored as-is - no trimming or normalization

Edge Cases:

  • Empty string is valid but rarely useful
  • Very long text may exceed provider token limits
  • Special characters and unicode are preserved
  • Newlines and whitespace are preserved

Performance Notes:

  • Immutable - safe for caching and reuse
  • No allocation overhead beyond string storage
  • Factory method has same cost as constructor

Exception Handling:

  • Constructor throws NullPointerException if text is null
  • No exceptions during normal field access

Related APIs:

  • Content - base interface
  • UserMessage - uses TextContent

Usage Examples:

import dev.langchain4j.data.message.TextContent;

// Simple text content
TextContent text1 = TextContent.from("Hello, world!");
TextContent text2 = new TextContent("Analyze this text.");

// Multiline text
TextContent text3 = TextContent.from(
    "Line 1\n" +
    "Line 2\n" +
    "Line 3"
);

// Text with special characters
TextContent text4 = TextContent.from("Price: $99.99 • Discount: 20%");

// Empty text (valid but not recommended)
TextContent emptyText = TextContent.from("");

ImageContent

Image content for multimodal messages with configurable detail level.

package dev.langchain4j.data.message;

import dev.langchain4j.data.image.Image;
import java.net.URI;

/**
 * Represents image content with detail level
 */
public class ImageContent implements Content {
    /**
     * Detail level for image processing
     */
    public enum DetailLevel {
        LOW,
        MEDIUM,
        HIGH,
        ULTRA_HIGH,
        AUTO
    }

    /**
     * Create from URL (LOW detail by default)
     * @param url Image URL
     */
    public ImageContent(URI url);

    /**
     * Create from URL string (LOW detail by default)
     * @param url Image URL
     */
    public ImageContent(String url);

    /**
     * Create from URL with detail level
     * @param url Image URL
     * @param detailLevel Detail level
     */
    public ImageContent(URI url, DetailLevel detailLevel);

    /**
     * Create from URL string with detail level
     * @param url Image URL
     * @param detailLevel Detail level
     */
    public ImageContent(String url, DetailLevel detailLevel);

    /**
     * Get image
     * @return Image instance
     */
    public Image image();

    /**
     * Get detail level
     * @return Detail level
     */
    public DetailLevel detailLevel();

    /**
     * Get content type
     * @return IMAGE content type
     */
    public ContentType type();

    /**
     * Create from URL (factory method)
     * @param url Image URL
     * @return ImageContent instance
     */
    public static ImageContent from(String url);

    /**
     * Create from URL with detail (factory method)
     * @param url Image URL
     * @param detailLevel Detail level
     * @return ImageContent instance
     */
    public static ImageContent from(String url, DetailLevel detailLevel);

    /**
     * Create from Image (factory method)
     * @param image Image instance
     * @return ImageContent instance
     */
    public static ImageContent from(Image image);

    /**
     * Create from Image with detail (factory method)
     * @param image Image instance
     * @param detailLevel Detail level
     * @return ImageContent instance
     */
    public static ImageContent from(Image image, DetailLevel detailLevel);
}

Thread Safety: ImageContent is immutable and thread-safe after construction. All fields are final.

Common Pitfalls:

  • Do NOT use HIGH/ULTRA_HIGH detail unnecessarily - costs more tokens and processing time
  • Do NOT assume all providers support all detail levels - some ignore this parameter
  • Do NOT pass extremely large base64 images - may exceed provider limits (typically 20MB)
  • Do NOT use unsupported image formats - stick to JPEG, PNG, GIF, WebP
  • Do NOT forget that HTTP URLs must be publicly accessible by the provider
  • Do NOT use LOW detail for images with fine text or detailed charts
  • Do NOT assume default detail level is optimal - explicitly set for best results

Edge Cases:

  • Default detail level is LOW (constructor without detail parameter)
  • AUTO detail level lets provider decide based on image characteristics
  • Some providers (OpenAI) map detail levels to specific pricing tiers
  • Base64 data URLs work but consume more tokens than HTTP URLs
  • Image URLs must be accessible at request time - no local file paths
  • Provider may resize/compress images regardless of detail level
  • Animated GIFs typically use only first frame

Performance Notes:

  • Higher detail levels consume more tokens and increase processing time
  • LOW detail: ~85 tokens per image (OpenAI)
  • HIGH detail: ~170-765 tokens depending on image size (OpenAI)
  • HTTP URLs are more efficient than base64 for repeated images
  • Image download time affects overall request latency for HTTP URLs
  • Base64 encoding increases request size by ~33%

Exception Handling:

  • Constructor throws NullPointerException if url is null
  • Constructor throws IllegalArgumentException if URL string is malformed
  • Factory methods throw NullPointerException if parameters are null
  • No exceptions during normal field access after construction

Related APIs:

  • Image - image data holder
  • Content - base interface
  • UserMessage - uses ImageContent for multimodal input

Usage Examples:

import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.TextContent;

// Simple image from URL (LOW detail default)
ImageContent img1 = ImageContent.from("https://example.com/photo.jpg");

// High detail for detailed analysis
ImageContent img2 = ImageContent.from(
    "https://example.com/chart.png",
    ImageContent.DetailLevel.HIGH
);

// Auto detail - let provider decide
ImageContent img3 = ImageContent.from(
    "https://example.com/document.jpg",
    ImageContent.DetailLevel.AUTO
);

// Base64 encoded image
String base64Image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...";
ImageContent img4 = ImageContent.from(base64Image);

// Using with UserMessage for image analysis
UserMessage msg1 = new UserMessage(
    TextContent.from("What's in this image?"),
    ImageContent.from("https://example.com/scene.jpg", ImageContent.DetailLevel.HIGH)
);

// Multiple images for comparison
UserMessage msg2 = new UserMessage(
    TextContent.from("Compare these two images"),
    ImageContent.from("https://example.com/before.jpg", ImageContent.DetailLevel.MEDIUM),
    ImageContent.from("https://example.com/after.jpg", ImageContent.DetailLevel.MEDIUM)
);

// Analyzing document/chart with high detail
UserMessage msg3 = new UserMessage(
    TextContent.from("Extract all text from this invoice"),
    ImageContent.from("https://example.com/invoice.png", ImageContent.DetailLevel.HIGH)
);

// Using Image object directly
Image imageObj = Image.builder()
    .url("https://example.com/photo.jpg")
    .build();
ImageContent img5 = ImageContent.from(imageObj, ImageContent.DetailLevel.LOW);

// Performance optimization: use LOW for simple images
ImageContent thumbnail = ImageContent.from(
    "https://example.com/thumbnail.jpg",
    ImageContent.DetailLevel.LOW  // Fast and cheap
);

// Accuracy optimization: use HIGH for complex images
ImageContent diagram = ImageContent.from(
    "https://example.com/architecture-diagram.png",
    ImageContent.DetailLevel.HIGH  // Better recognition of details
);

AudioContent

Audio content for multimodal messages.

package dev.langchain4j.data.message;

/**
 * Represents audio content
 */
public class AudioContent implements Content {
    /**
     * Get content type
     * @return AUDIO content type
     */
    public ContentType type();
}

Thread Safety: Implementation details depend on concrete class. Typically immutable and thread-safe.

Common Pitfalls:

  • Do NOT assume all providers support audio - very limited support currently
  • Do NOT use without checking provider capabilities first
  • Do NOT assume specific audio formats - check provider documentation
  • Do NOT expect real-time audio streaming - typically batch processing only

Edge Cases:

  • Very limited provider support as of current version
  • Audio transcription vs audio understanding may be separate features
  • File size limits vary by provider
  • Supported formats vary by provider

Performance Notes:

  • Audio processing is typically slower than text/image
  • Large audio files may have significant upload time
  • Token consumption depends on audio duration

Exception Handling:

  • Implementation-specific - check concrete class documentation

Related APIs:

  • Content - base interface
  • UserMessage - can contain audio content

VideoContent

Video content for multimodal messages.

package dev.langchain4j.data.message;

/**
 * Represents video content
 */
public class VideoContent implements Content {
    /**
     * Get content type
     * @return VIDEO content type
     */
    public ContentType type();
}

Thread Safety: Implementation details depend on concrete class. Typically immutable and thread-safe.

Common Pitfalls:

  • Do NOT assume all providers support video - very limited support
  • Do NOT use without checking provider capabilities first
  • Do NOT expect real-time video processing - typically batch only
  • Do NOT assume all video formats are supported - check documentation

Edge Cases:

  • Very limited provider support (Gemini has some support)
  • Video frame sampling strategies vary by provider
  • File size limits are typically strict
  • Processing time can be substantial for long videos

Performance Notes:

  • Video processing is slowest of all content types
  • Large video files have significant upload time
  • Token consumption depends on video length and sampling rate

Exception Handling:

  • Implementation-specific - check concrete class documentation

Related APIs:

  • Content - base interface
  • UserMessage - can contain video content

PdfFileContent

PDF file content for multimodal messages.

package dev.langchain4j.data.message;

/**
 * Represents PDF file content
 */
public class PdfFileContent implements Content {
    /**
     * Get content type
     * @return PDF_FILE content type
     */
    public ContentType type();
}

Thread Safety: Implementation details depend on concrete class. Typically immutable and thread-safe.

Common Pitfalls:

  • Do NOT assume all providers support PDF - limited support
  • Do NOT expect perfect text extraction from complex PDFs
  • Do NOT use for large PDFs - page limits typically apply
  • Do NOT assume images/tables in PDF are processed - depends on provider

Edge Cases:

  • Limited provider support (some Gemini models)
  • Page limits vary by provider (e.g., 10 pages max)
  • Scanned PDFs may require OCR capabilities
  • Complex layouts may not extract correctly

Performance Notes:

  • PDF processing time depends on page count and complexity
  • Text extraction is typically fast
  • Image/table extraction slower if supported

Exception Handling:

  • Implementation-specific - check concrete class documentation

Related APIs:

  • Content - base interface
  • UserMessage - can contain PDF content

Testing Patterns

Unit Testing Messages

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import dev.langchain4j.data.message.*;

class MessageTest {

    @Test
    void testUserMessageCreation() {
        UserMessage msg = UserMessage.from("Hello");
        assertNotNull(msg);
        assertEquals(ChatMessageType.USER, msg.type());
        assertTrue(msg.hasSingleText());
        assertEquals("Hello", msg.singleText());
    }

    @Test
    void testSingleTextThrowsOnMultipleContents() {
        UserMessage msg = new UserMessage(
            TextContent.from("Text 1"),
            TextContent.from("Text 2")
        );
        assertFalse(msg.hasSingleText());
        assertThrows(RuntimeException.class, msg::singleText);
    }

    @Test
    void testMultimodalMessage() {
        UserMessage msg = new UserMessage(
            TextContent.from("Analyze this"),
            ImageContent.from("https://example.com/image.jpg")
        );
        assertEquals(2, msg.contents().size());
        assertEquals(ContentType.TEXT, msg.contents().get(0).type());
        assertEquals(ContentType.IMAGE, msg.contents().get(1).type());
    }

    @Test
    void testAiMessageWithToolRequests() {
        ToolExecutionRequest req = ToolExecutionRequest.builder()
            .id("call_123")
            .name("getTool")
            .arguments("{}")
            .build();

        AiMessage msg = new AiMessage(List.of(req));
        assertTrue(msg.hasToolExecutionRequests());
        assertEquals(1, msg.toolExecutionRequests().size());
        assertNull(msg.text());
    }

    @Test
    void testSystemMessageFinders() {
        List<ChatMessage> messages = List.of(
            SystemMessage.from("System 1"),
            UserMessage.from("User"),
            SystemMessage.from("System 2")
        );

        assertTrue(SystemMessage.findFirst(messages).isPresent());
        assertEquals("System 1", SystemMessage.findFirst(messages).get().text());
        assertEquals("System 2", SystemMessage.findLast(messages).get().text());
        assertEquals(2, SystemMessage.findAll(messages).size());
    }

    @Test
    void testToolExecutionResultMessage() {
        ToolExecutionRequest req = ToolExecutionRequest.builder()
            .id("call_123")
            .name("testTool")
            .arguments("{}")
            .build();

        ToolExecutionResultMessage result =
            ToolExecutionResultMessage.from(req, "success");

        assertEquals("call_123", result.id());
        assertEquals("testTool", result.toolName());
        assertEquals("success", result.text());
    }

    @Test
    void testMessageImmutability() {
        AiMessage original = AiMessage.from("Original");
        AiMessage modified = original.withText("Modified");

        assertEquals("Original", original.text());
        assertEquals("Modified", modified.text());
        assertNotSame(original, modified);
    }

    @Test
    void testAttributesMutability() {
        UserMessage msg = UserMessage.from("Test");
        msg.attributes().put("key", "value");
        assertEquals("value", msg.attributes().get("key"));
    }
}

Integration Testing with Mock LLM

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.data.message.*;
import java.util.List;

class ChatIntegrationTest {

    @Test
    void testBasicConversationFlow() {
        // Mock LLM
        ChatLanguageModel model = mock(ChatLanguageModel.class);
        AiMessage response = AiMessage.from("The capital is Paris.");
        when(model.generate(anyList())).thenReturn(response);

        // Create conversation
        List<ChatMessage> messages = List.of(
            SystemMessage.from("You are a helpful assistant."),
            UserMessage.from("What is the capital of France?")
        );

        // Get response
        AiMessage result = model.generate(messages);
        assertEquals("The capital is Paris.", result.text());
        verify(model, times(1)).generate(messages);
    }

    @Test
    void testToolExecutionFlow() {
        ChatLanguageModel model = mock(ChatLanguageModel.class);

        // Mock tool request response
        ToolExecutionRequest toolReq = ToolExecutionRequest.builder()
            .id("call_123")
            .name("getWeather")
            .arguments("{\"location\":\"Paris\"}")
            .build();
        AiMessage toolRequest = new AiMessage(List.of(toolReq));

        // Mock final response after tool execution
        AiMessage finalResponse = AiMessage.from("It's sunny in Paris.");

        when(model.generate(anyList()))
            .thenReturn(toolRequest)
            .thenReturn(finalResponse);

        // Initial request
        List<ChatMessage> messages = new ArrayList<>();
        messages.add(UserMessage.from("What's the weather in Paris?"));
        AiMessage response1 = model.generate(messages);

        // Execute tool and add result
        if (response1.hasToolExecutionRequests()) {
            messages.add(response1);
            for (ToolExecutionRequest req : response1.toolExecutionRequests()) {
                String toolResult = executeWeatherTool(req.arguments());
                messages.add(ToolExecutionResultMessage.from(req, toolResult));
            }

            // Get final response
            AiMessage response2 = model.generate(messages);
            assertEquals("It's sunny in Paris.", response2.text());
        }
    }

    private String executeWeatherTool(String arguments) {
        return "{\"temperature\":\"72F\",\"condition\":\"sunny\"}";
    }
}

Validation Testing

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class MessageValidationTest {

    @Test
    void testNullTextThrows() {
        assertThrows(NullPointerException.class,
            () -> new UserMessage((String) null));
        assertThrows(NullPointerException.class,
            () -> new SystemMessage(null));
    }

    @Test
    void testEmptyTextAllowed() {
        assertDoesNotThrow(() -> new UserMessage(""));
        assertDoesNotThrow(() -> new SystemMessage(""));
    }

    @Test
    void testNullContentListThrows() {
        assertThrows(NullPointerException.class,
            () -> new UserMessage((List<Content>) null));
    }

    @Test
    void testEmptyContentListAllowed() {
        assertDoesNotThrow(() -> new UserMessage(List.of()));
    }

    @Test
    void testInvalidImageUrl() {
        assertThrows(IllegalArgumentException.class,
            () -> new ImageContent("not a valid url"));
    }
}

Message Validation Patterns

Basic Validation

import dev.langchain4j.data.message.*;
import java.util.List;

public class MessageValidator {

    /**
     * Validate that a message list is suitable for LLM processing
     */
    public static void validateConversation(List<ChatMessage> messages) {
        if (messages == null || messages.isEmpty()) {
            throw new IllegalArgumentException("Messages cannot be null or empty");
        }

        // Check for system message placement
        for (int i = 0; i < messages.size(); i++) {
            ChatMessage msg = messages.get(i);
            if (msg.type() == ChatMessageType.SYSTEM && i > 0) {
                // Some providers require system message first
                System.err.println("Warning: System message at position " + i);
            }
        }

        // Validate tool execution flow
        validateToolExecutionFlow(messages);

        // Check for content issues
        for (ChatMessage msg : messages) {
            if (msg instanceof UserMessage) {
                validateUserMessage((UserMessage) msg);
            }
        }
    }

    /**
     * Validate UserMessage content
     */
    private static void validateUserMessage(UserMessage msg) {
        if (msg.contents().isEmpty()) {
            throw new IllegalArgumentException("UserMessage has no content");
        }

        // Check for unsupported content combinations
        boolean hasText = false;
        boolean hasImage = false;
        boolean hasOther = false;

        for (Content content : msg.contents()) {
            switch (content.type()) {
                case TEXT:
                    hasText = true;
                    TextContent text = (TextContent) content;
                    if (text.text().trim().isEmpty()) {
                        System.err.println("Warning: Empty text content");
                    }
                    break;
                case IMAGE:
                    hasImage = true;
                    break;
                default:
                    hasOther = true;
            }
        }

        if (hasOther && !hasText) {
            System.err.println("Warning: Non-text content without text description");
        }
    }

    /**
     * Validate tool execution request/result pairing
     */
    private static void validateToolExecutionFlow(List<ChatMessage> messages) {
        for (int i = 0; i < messages.size(); i++) {
            ChatMessage msg = messages.get(i);

            if (msg.type() == ChatMessageType.TOOL_EXECUTION_RESULT) {
                // Verify there's a preceding AI message with tool requests
                if (i == 0) {
                    throw new IllegalArgumentException(
                        "ToolExecutionResultMessage without preceding AiMessage");
                }

                ChatMessage prev = messages.get(i - 1);
                if (prev.type() != ChatMessageType.AI) {
                    throw new IllegalArgumentException(
                        "ToolExecutionResultMessage must follow AiMessage");
                }

                AiMessage aiMsg = (AiMessage) prev;
                if (!aiMsg.hasToolExecutionRequests()) {
                    throw new IllegalArgumentException(
                        "ToolExecutionResultMessage without tool request");
                }

                // Verify ID matching
                ToolExecutionResultMessage result = (ToolExecutionResultMessage) msg;
                boolean foundMatchingRequest = aiMsg.toolExecutionRequests().stream()
                    .anyMatch(req -> req.id().equals(result.id()));

                if (!foundMatchingRequest) {
                    throw new IllegalArgumentException(
                        "ToolExecutionResultMessage ID doesn't match any request");
                }
            }
        }
    }

    /**
     * Estimate token count for message list
     * Rough approximation - actual count depends on provider
     */
    public static int estimateTokenCount(List<ChatMessage> messages) {
        int count = 0;

        for (ChatMessage msg : messages) {
            if (msg instanceof UserMessage) {
                UserMessage userMsg = (UserMessage) msg;
                for (Content content : userMsg.contents()) {
                    if (content instanceof TextContent) {
                        String text = ((TextContent) content).text();
                        count += text.length() / 4; // Rough estimate
                    } else if (content instanceof ImageContent) {
                        ImageContent img = (ImageContent) content;
                        // Rough token estimates based on detail level
                        switch (img.detailLevel()) {
                            case LOW:
                                count += 85;
                                break;
                            case HIGH:
                            case ULTRA_HIGH:
                                count += 500; // Average
                                break;
                            default:
                                count += 200;
                        }
                    }
                }
            } else if (msg instanceof AiMessage) {
                AiMessage aiMsg = (AiMessage) msg;
                if (aiMsg.text() != null) {
                    count += aiMsg.text().length() / 4;
                }
            } else if (msg instanceof SystemMessage) {
                SystemMessage sysMsg = (SystemMessage) msg;
                count += sysMsg.text().length() / 4;
            }
        }

        return count;
    }
}

Usage of Validation

import dev.langchain4j.data.message.*;
import java.util.List;

public class ValidationExample {

    public void processConversation(List<ChatMessage> messages) {
        // Validate before sending to LLM
        try {
            MessageValidator.validateConversation(messages);
        } catch (IllegalArgumentException e) {
            System.err.println("Invalid conversation: " + e.getMessage());
            return;
        }

        // Check token count
        int estimatedTokens = MessageValidator.estimateTokenCount(messages);
        if (estimatedTokens > 4000) {
            System.err.println("Warning: Estimated " + estimatedTokens +
                             " tokens, may exceed context window");
        }

        // Proceed with LLM call
        // chatModel.generate(messages);
    }
}

Related APIs

Cross-References

  • Tool Execution: ToolExecutionRequest, ToolExecutionResultMessage work together for agentic workflows
  • Image Handling: Image class used by ImageContent for multimodal support
  • Chat Models: ChatLanguageModel interface consumes these message types
  • Prompt Templates: PromptTemplate can generate UserMessage instances
  • Memory: ChatMemory stores lists of ChatMessage for conversation history
  • Response: Response<AiMessage> wraps AI responses with metadata

API Dependencies

ChatMessage (interface)
├── UserMessage
│   ├── Content (interface)
│   │   ├── TextContent
│   │   ├── ImageContent
│   │   │   └── Image
│   │   ├── AudioContent
│   │   ├── VideoContent
│   │   └── PdfFileContent
│   └── Map<String, Object> attributes
├── AiMessage
│   ├── ToolExecutionRequest
│   └── Image (Gemini)
├── SystemMessage
└── ToolExecutionResultMessage
    └── ToolExecutionRequest (reference)

Common Integration Points

  1. Chat Models: All models accept List<ChatMessage> and return AiMessage
  2. Memory Systems: Store and retrieve ChatMessage instances
  3. Streaming: StreamingChatLanguageModel emits partial AiMessage updates
  4. Tools: Agent framework uses AiMessage.toolExecutionRequests() and ToolExecutionResultMessage
  5. RAG: Retrieved documents often injected as UserMessage with context
  6. Templates: Generate UserMessage or SystemMessage from templates

Summary

The LangChain4j message API provides a comprehensive type-safe system for chat interactions:

  • Type Safety: Strong typing via ChatMessage interface and concrete implementations
  • Immutability: Most message types are immutable (except attributes() map)
  • Multimodal: Full support for text, images, audio, video, and PDF content
  • Tool Integration: First-class support for tool execution via request/result messages
  • Provider Agnostic: Works across different LLM providers with graceful degradation
  • Thread Safety: Read operations safe, write to attributes requires synchronization
  • Validation: Multiple validation patterns to ensure correct message structure
  • Testing: Comprehensive testing patterns for unit and integration tests

Key principles for agents:

  1. Always validate message structure before LLM calls
  2. Use hasSingleText() before calling singleText()
  3. Match tool request IDs with result IDs exactly
  4. Choose appropriate image detail levels for performance vs accuracy
  5. Handle null returns gracefully (name, text, thinking, isError)
  6. Synchronize access to mutable attributes() map
  7. Consider token costs when choosing content types and detail levels
  8. Test edge cases: empty content, null fields, invalid sequences

Install with Tessl CLI

npx tessl i tessl/maven-dev-langchain4j--langchain4j

docs

ai-services.md

chains.md

classification.md

data-types.md

document-processing.md

embedding-store.md

guardrails.md

index.md

memory.md

messages.md

models.md

output-parsing.md

prompts.md

rag.md

request-response.md

spi.md

tools.md

README.md

tile.json