Build LLM-powered applications in Java with support for chatbots, agents, RAG, tools, and much more
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.
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:
Edge Cases:
type() method, not instanceof when possiblePerformance Notes:
type() is typically faster than instanceofException Handling:
type() should never throw exceptions in standard implementationsRelated APIs:
ChatMessageType - enum for message type identificationUserMessage, AiMessage, SystemMessage, ToolExecutionResultMessage - concrete implementationspackage 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:
Edge Cases:
CUSTOM type is for extension scenarios - not used by core libraryPerformance Notes:
Exception Handling:
Related APIs:
ChatMessage.type() - returns this enum valueRepresents 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:
singleText() without checking hasSingleText() first - throws RuntimeExceptionname() is non-null - it's optional and can return nullattributes() map from multiple threads without synchronizationEdge Cases:
Performance Notes:
singleText() performs type checking and extraction - not cachedattributes() returns mutable map reference - no copyingfrom(), userMessage()) have same cost as constructorsException Handling:
singleText() throws RuntimeException if message doesn't have exactly one TextContentNullPointerException if text parameter is null (in text-only constructors)NullPointerException if contents parameter is nullbuild() may throw IllegalStateException if required fields not setRelated APIs:
Content - base interface for message contentTextContent - text content typeImageContent - image content with detail levelsAudioContent, VideoContent, PdfFileContent - other multimodal typesChatMessage - base interfaceUsage 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());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:
text() is non-null - can be null when only tool execution requests presentthinking() is populated - only some models (Claude with thinking enabled) provide thistoolExecutionRequests() is null when empty - returns empty list, not nullhasToolExecutionRequests() before processing toolsimages() is supported - only Gemini models currently populate thiswithText() for mutations in-place - it returns a NEW instanceEdge Cases:
Performance Notes:
withText() creates a new AiMessage instance - O(1) if using builder pattern internallyhasToolExecutionRequests() is O(1) check (not list traversal)Exception Handling:
NullPointerException if required parameters are nullwithText() throws NullPointerException if text parameter is nullbuild() may throw IllegalStateException if in invalid stateIndexOutOfBoundsExceptionRelated APIs:
ToolExecutionRequest - represents a tool call request from AIToolExecutionResultMessage - response to tool executionImage - generated image data (Gemini-specific)ChatMessage - base interfaceUsage 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();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:
Edge Cases:
Performance Notes:
findFirst() and findLast() return empty Optional if not found - no exception overheadException Handling:
NullPointerException if text is nullNullPointerException if messages list is nullRelated APIs:
ChatMessage - base interfaceUserMessage - user input messagesAiMessage - AI response messagesUsage 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"));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:
isError() is always set - can be null (unknown state)Edge Cases:
Performance Notes:
from(ToolExecutionRequest, String) is preferred over constructorException Handling:
NullPointerException if id, toolName, or text is nullNullPointerException if request or result is nullbuild() throws IllegalStateException if required fields not setRelated APIs:
ToolExecutionRequest - the request this result responds toAiMessage.toolExecutionRequests() - source of tool requestsChatMessage - base interfaceUsage 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());
}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:
Edge Cases:
type() method when possiblePerformance Notes:
type() is efficientException Handling:
type() should not throw exceptions in standard implementationsRelated APIs:
ContentType - enum for content type identificationTextContent, ImageContent, AudioContent, VideoContent, PdfFileContent - implementationspackage 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:
Edge Cases:
Performance Notes:
Exception Handling:
Related APIs:
Content.type() - returns this enum valueText 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:
Edge Cases:
Performance Notes:
Exception Handling:
NullPointerException if text is nullRelated APIs:
Content - base interfaceUserMessage - uses TextContentUsage 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("");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:
Edge Cases:
Performance Notes:
Exception Handling:
NullPointerException if url is nullIllegalArgumentException if URL string is malformedNullPointerException if parameters are nullRelated APIs:
Image - image data holderContent - base interfaceUserMessage - uses ImageContent for multimodal inputUsage 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
);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:
Edge Cases:
Performance Notes:
Exception Handling:
Related APIs:
Content - base interfaceUserMessage - can contain audio contentVideo 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:
Edge Cases:
Performance Notes:
Exception Handling:
Related APIs:
Content - base interfaceUserMessage - can contain video contentPDF 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:
Edge Cases:
Performance Notes:
Exception Handling:
Related APIs:
Content - base interfaceUserMessage - can contain PDF contentimport 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"));
}
}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\"}";
}
}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"));
}
}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;
}
}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);
}
}ToolExecutionRequest, ToolExecutionResultMessage work together for agentic workflowsImage class used by ImageContent for multimodal supportChatLanguageModel interface consumes these message typesPromptTemplate can generate UserMessage instancesChatMemory stores lists of ChatMessage for conversation historyResponse<AiMessage> wraps AI responses with metadataChatMessage (interface)
├── UserMessage
│ ├── Content (interface)
│ │ ├── TextContent
│ │ ├── ImageContent
│ │ │ └── Image
│ │ ├── AudioContent
│ │ ├── VideoContent
│ │ └── PdfFileContent
│ └── Map<String, Object> attributes
├── AiMessage
│ ├── ToolExecutionRequest
│ └── Image (Gemini)
├── SystemMessage
└── ToolExecutionResultMessage
└── ToolExecutionRequest (reference)List<ChatMessage> and return AiMessageChatMessage instancesStreamingChatLanguageModel emits partial AiMessage updatesAiMessage.toolExecutionRequests() and ToolExecutionResultMessageUserMessage with contextUserMessage or SystemMessage from templatesThe LangChain4j message API provides a comprehensive type-safe system for chat interactions:
ChatMessage interface and concrete implementationsattributes() map)Key principles for agents:
hasSingleText() before calling singleText()attributes() mapInstall with Tessl CLI
npx tessl i tessl/maven-dev-langchain4j--langchain4j