Core classes and interfaces of LangChain4j providing foundational abstractions for LLM interaction, RAG, embeddings, agents, and observability
Package: dev.langchain4j.data.message
Thread-Safety: All message types are immutable and thread-safe
Multimodal: Supports text, images, audio, video, and PDF content
Chat messages represent the fundamental building blocks of conversations in LangChain4j, supporting both simple text and rich multimodal content.
package dev.langchain4j.data.message;
/**
* Base interface for all message types
* Immutability: All implementations are immutable and thread-safe
*/
public interface ChatMessage {
/**
* Get message type
* @return Message type enum
*/
ChatMessageType type();
/**
* Get message text content
* For multimodal messages, returns text portion only
* @return Text content (may be null for some message types)
*/
String text();
}package dev.langchain4j.data.message;
public enum ChatMessageType {
SYSTEM, // System instructions to model
USER, // User input to model
AI, // Model response to user
TOOL_EXECUTION_RESULT // Result of tool execution
}package dev.langchain4j.data.message;
/**
* System instructions that guide model behavior
* Typically placed at start of conversation
* Immutability: Immutable, thread-safe
*/
public class SystemMessage implements ChatMessage {
private final String text;
public static SystemMessage from(String text) { /* ... */ }
public static SystemMessage systemMessage(String text) { /* ... */ }
@Override
public ChatMessageType type() { return ChatMessageType.SYSTEM; }
@Override
public String text() { return text; }
}package dev.langchain4j.data.message;
/**
* User input to the model
* Supports multimodal content (text, images, audio, video, PDFs)
* Immutability: Immutable, thread-safe
*/
public class UserMessage implements ChatMessage {
private final List<Content> contents; // Can contain text, images, etc.
private final String name; // Optional user identifier
// Text-only constructors
public static UserMessage from(String text) { /* ... */ }
public static UserMessage userMessage(String text) { /* ... */ }
// Multimodal constructors
public static UserMessage from(Content... contents) { /* ... */ }
public static UserMessage from(List<Content> contents) { /* ... */ }
// With user name
public static UserMessage from(String name, String text) { /* ... */ }
public static UserMessage from(String name, Content... contents) { /* ... */ }
@Override
public ChatMessageType type() { return ChatMessageType.USER; }
@Override
public String text() { /* Returns text from TextContent */ }
public List<Content> contents() { return contents; }
public String name() { return name; }
}package dev.langchain4j.data.message;
import dev.langchain4j.agent.tool.ToolExecutionRequest;
/**
* Model response to user
* Can contain text and/or tool execution requests
* Immutability: Immutable, thread-safe
*/
public class AiMessage implements ChatMessage {
private final String text;
private final List<ToolExecutionRequest> toolExecutionRequests;
public static AiMessage from(String text) { /* ... */ }
public static AiMessage aiMessage(String text) { /* ... */ }
// With tool requests
public static AiMessage from(ToolExecutionRequest... requests) { /* ... */ }
public static AiMessage from(String text, List<ToolExecutionRequest> requests) { /* ... */ }
@Override
public ChatMessageType type() { return ChatMessageType.AI; }
@Override
public String text() { return text; }
public List<ToolExecutionRequest> toolExecutionRequests() { return toolExecutionRequests; }
public boolean hasToolExecutionRequests() {
return toolExecutionRequests != null && !toolExecutionRequests.isEmpty();
}
}package dev.langchain4j.data.message;
/**
* Result of tool execution, sent back to model
* Links to original tool request via ID
* Immutability: Immutable, thread-safe
*/
public class ToolExecutionResultMessage implements ChatMessage {
private final String id; // Matches ToolExecutionRequest.id()
private final String toolName;
private final String text; // Tool result as text
public static ToolExecutionResultMessage from(
String id,
String toolName,
String result
) { /* ... */ }
public static ToolExecutionResultMessage from(
ToolExecutionRequest request,
String result
) { /* ... */ }
@Override
public ChatMessageType type() { return ChatMessageType.TOOL_EXECUTION_RESULT; }
@Override
public String text() { return text; }
public String id() { return id; }
public String toolName() { return toolName; }
}package dev.langchain4j.data.message;
/**
* Base interface for message content
* Immutability: All implementations are immutable
*/
public interface Content {
/**
* Get content type
* @return Content type enum
*/
ContentType type();
}
public enum ContentType {
TEXT,
IMAGE,
AUDIO,
VIDEO,
PDF
}package dev.langchain4j.data.message;
public class TextContent implements Content {
private final String text;
public static TextContent from(String text) { /* ... */ }
@Override
public ContentType type() { return ContentType.TEXT; }
public String text() { return text; }
}package dev.langchain4j.data.message;
import java.net.URI;
public class ImageContent implements Content {
private final URI imageUrl; // OR
private final String base64Data; // Base64-encoded image
private final String mimeType; // e.g., "image/png"
// From URL
public static ImageContent from(String imageUrl) { /* ... */ }
public static ImageContent from(URI imageUrl) { /* ... */ }
// From base64 data
public static ImageContent from(String base64Data, String mimeType) { /* ... */ }
@Override
public ContentType type() { return ContentType.IMAGE; }
public URI imageUrl() { return imageUrl; }
public String base64Data() { return base64Data; }
public String mimeType() { return mimeType; }
}package dev.langchain4j.data.message;
public class AudioContent implements Content {
private final URI audioUrl; // OR
private final String base64Data; // Base64-encoded audio
private final String mimeType; // e.g., "audio/mp3"
public static AudioContent from(String audioUrl) { /* ... */ }
public static AudioContent from(String base64Data, String mimeType) { /* ... */ }
@Override
public ContentType type() { return ContentType.AUDIO; }
}package dev.langchain4j.data.message;
public class VideoContent implements Content {
private final URI videoUrl;
private final String base64Data;
private final String mimeType; // e.g., "video/mp4"
public static VideoContent from(String videoUrl) { /* ... */ }
public static VideoContent from(String base64Data, String mimeType) { /* ... */ }
@Override
public ContentType type() { return ContentType.VIDEO; }
}package dev.langchain4j.data.message;
public class PdfFileContent implements Content {
private final URI pdfUrl;
private final String base64Data;
public static PdfFileContent from(String pdfUrl) { /* ... */ }
public static PdfFileContent from(String base64Data) { /* ... */ }
@Override
public ContentType type() { return ContentType.PDF; }
}import dev.langchain4j.data.message.*;
import java.util.List;
import java.util.ArrayList;
// Build conversation
List<ChatMessage> messages = new ArrayList<>();
// System instruction
messages.add(SystemMessage.from("You are a helpful assistant."));
// User message
messages.add(UserMessage.from("What is the capital of France?"));
// AI response
messages.add(AiMessage.from("The capital of France is Paris."));
// Next user turn
messages.add(UserMessage.from("What about Spain?"));
// Send to model
ChatResponse response = chatModel.chat(messages);import dev.langchain4j.data.message.*;
// Text + Image
UserMessage multimodal = UserMessage.from(
TextContent.from("What's in this image?"),
ImageContent.from("https://example.com/image.jpg")
);
// Multiple images
UserMessage compareImages = UserMessage.from(
TextContent.from("Compare these two images:"),
ImageContent.from("https://example.com/image1.jpg"),
ImageContent.from("https://example.com/image2.jpg")
);
// Image from file (base64)
byte[] imageData = Files.readAllBytes(Path.of("photo.jpg"));
String base64 = Base64.getEncoder().encodeToString(imageData);
UserMessage localImage = UserMessage.from(
TextContent.from("Describe this photo"),
ImageContent.from(base64, "image/jpeg")
);import dev.langchain4j.agent.tool.ToolExecutionRequest;
// 1. User asks a question
messages.add(UserMessage.from("What's the weather in Paris?"));
// 2. Model responds with tool call
ChatResponse response = chatModel.chat(messages);
AiMessage aiMessage = response.aiMessage();
messages.add(aiMessage);
if (aiMessage.hasToolExecutionRequests()) {
for (ToolExecutionRequest request : aiMessage.toolExecutionRequests()) {
// 3. Execute tool
String result = executeTool(request);
// 4. Add tool result to conversation
ToolExecutionResultMessage toolResult = ToolExecutionResultMessage.from(
request,
result
);
messages.add(toolResult);
}
// 5. Send back to model for final response
ChatResponse finalResponse = chatModel.chat(messages);
System.out.println(finalResponse.aiMessage().text());
}// Distinguish between users in group chat
messages.add(UserMessage.from("Alice", "I think we should use React"));
messages.add(UserMessage.from("Bob", "I prefer Vue.js"));
ChatResponse response = chatModel.chat(messages);
// Model can reference users by name in response// ✅ GOOD: System message at the start
List<ChatMessage> messages = new ArrayList<>();
messages.add(SystemMessage.from("You are a helpful coding assistant."));
messages.add(UserMessage.from("Help me debug this code"));
// ❌ BAD: System message in the middle (model may ignore it)
messages.add(UserMessage.from("Question 1"));
messages.add(SystemMessage.from("Be concise")); // Too late!// Keep history within context window limits
private static final int MAX_MESSAGES = 20;
if (messages.size() > MAX_MESSAGES) {
// Keep system message + recent history
ChatMessage systemMsg = messages.get(0);
List<ChatMessage> recentMsgs = messages.subList(
messages.size() - MAX_MESSAGES + 1,
messages.size()
);
messages.clear();
messages.add(systemMsg);
messages.addAll(recentMsgs);
}// Messages are immutable - safe to share across threads
UserMessage userMsg = UserMessage.from("Hello");
// Both threads use same message instance safely
executor.submit(() -> chatModel1.chat(List.of(userMsg)));
executor.submit(() -> chatModel2.chat(List.of(userMsg)));for (Content content : userMessage.contents()) {
switch (content.type()) {
case TEXT:
TextContent text = (TextContent) content;
processText(text.text());
break;
case IMAGE:
ImageContent image = (ImageContent) content;
processImage(image);
break;
case AUDIO:
AudioContent audio = (AudioContent) content;
processAudio(audio);
break;
}
}| Content Type | OpenAI | Anthropic | Azure | |
|---|---|---|---|---|
| Text | ✅ | ✅ | ✅ | ✅ |
| Images | ✅ GPT-4V | ✅ Claude 3+ | ✅ Gemini | ✅ |
| Audio | ✅ GPT-4o | ❌ | ✅ Gemini | ✅ |
| Video | ❌ | ❌ | ✅ Gemini | ❌ |
| ❌ | ✅ Claude 3+ | ✅ Gemini | ❌ |
| Pitfall | Solution |
|---|---|
| Modifying message list during iteration | Create new list or use iterator.remove() |
| System message not at start | Place system message first |
| Forgetting to add AI response to history | Always append AI message before next user turn |
| Not handling null text | Check text() != null for messages |
| Exceeding context window | Implement message pruning strategy |
// Messages are serializable for persistence
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper mapper = new ObjectMapper();
// Serialize
String json = mapper.writeValueAsString(messages);
// Deserialize
List<ChatMessage> loaded = mapper.readValue(
json,
new TypeReference<List<ChatMessage>>() {}
);Install with Tessl CLI
npx tessl i tessl/maven-dev-langchain4j--langchain4j-core