[BUG] AgenticScopeSerializer cannot round-trip UserMessage stored directly in AgenticScope state
Describe the bug
AgenticScopeSerializer cannot deserialize an AgenticScope when a UserMessage is stored directly in the scope state.
Serialization succeeds, but the generated JSON contains:
"type": "USER"inside the serialized UserMessage.
During deserialization, Jackson uses UserMessage.Builder, which does not accept a type property, so AgenticScopeSerializer.fromJson() fails with an UnrecognizedPropertyException.
The issue is reproducible on a plain JVM, without Quarkus, persistence, or native-image.
A similar problem can also be reproduced when TextContent or AiMessage are stored directly as arbitrary scope state values.
Log and Stack trace
Generated JSON for a UserMessage stored in scope state:
{
"memoryId": "...",
"kind": "EPHEMERAL",
"state": [
"java.util.concurrent.ConcurrentHashMap",
{
"candidate": [
"dev.langchain4j.data.message.UserMessage",
{
"contents": [
"java.util.Collections$UnmodifiableRandomAccessList",
[
{
"text": "hello",
"type": "TEXT"
}
]
],
"type": "USER"
}
]
}
],
"agentInvocations": [
"java.util.Collections$SynchronizedRandomAccessList",
[]
],
"context": [
"java.util.Collections$SynchronizedRandomAccessList",
[]
]
}AgenticScopeSerializer.fromJson() then fails with:
java.lang.RuntimeException: Failed to deserialize AgenticScope from JSON
at dev.langchain4j.agentic.scope.DefaultAgenticScopeJsonCodec.fromJson(DefaultAgenticScopeJsonCodec.java:53)
at dev.langchain4j.agentic.scope.AgenticScopeSerializer.fromJson(AgenticScopeSerializer.java:45)
Caused by: java.lang.RuntimeException:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "type"
(class dev.langchain4j.data.message.UserMessage$Builder),
not marked as ignorable
(3 known properties: "contents", "name", "attributes")
through reference chain:
dev.langchain4j.agentic.scope.DefaultAgenticScope["state"]
-> java.util.concurrent.ConcurrentHashMap["candidate"]
-> dev.langchain4j.data.message.UserMessage$Builder["type"]To Reproduce
Minimal JUnit reproducer:
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import org.junit.jupiter.api.Test;
import dev.langchain4j.agentic.scope.AgenticScopeSerializer;
import dev.langchain4j.agentic.scope.DefaultAgenticScope;
import dev.langchain4j.data.message.UserMessage;
class AgenticScopeSerializationTest {
@Test
void shouldRoundTripUserMessageInAgenticScope() {
DefaultAgenticScope scope =
DefaultAgenticScope.ephemeralAgenticScope();
scope.writeState(
"candidate",
UserMessage.from("hello"));
String json = AgenticScopeSerializer.toJson(scope);
System.out.println(json);
DefaultAgenticScope restored =
AgenticScopeSerializer.fromJson(json);
assertInstanceOf(
UserMessage.class,
restored.readState("candidate"));
}
}The failure happens on:
AgenticScopeSerializer.fromJson(json);Expected behavior
Anything serialized successfully by:
AgenticScopeSerializer.toJson(scope)should be deserializable by:
AgenticScopeSerializer.fromJson(json)The restored "candidate" value should be a UserMessage equivalent to the original one.
Please complete the following information:
LangChain4j version:
dev.langchain4j:langchain4j:1.20.0dev.langchain4j:langchain4j-agentic:1.20.0-beta30
LLM(s) used: N/A — no LLM call is required to reproduce the issue
Java version: Java 21
Spring Boot version (if applicable): N/A
Additional context
I originally found this while testing persistence of LangChain4j AgenticScope state, but the minimal reproducer above confirms that the problem exists independently of persistence and can be reproduced on a normal JVM.
There is an interesting distinction depending on where the message is stored.
When a UserMessage is stored directly as arbitrary AgenticScope state, deserialization fails:
scope.writeState(
"candidate",
UserMessage.from("hello"));However, when the same message is wrapped in DefaultAgenticScope.AgentMessage, which is closer to how messages are normally stored in the AgenticScope context, the JVM round-trip succeeds:
scope.writeState(
"candidate",
new DefaultAgenticScope.AgentMessage(
"agent",
"agent$0",
UserMessage.from("hello")));The resulting JSON contains the polymorphic discriminator:
"message": {
"contents": [
"java.util.Collections$UnmodifiableRandomAccessList",
[
{
"text": "hello",
"type": "TEXT"
}
]
],
"type": "USER"
}and this successfully deserializes when the declared property is the polymorphic ChatMessage type.
This suggests the issue may be related to the interaction between:
- the polymorphic type information managed by
JacksonStateJsonCodec, and - the
type()discriminator exposed by LangChain4j message/content classes.
When the concrete class itself is stored directly as arbitrary state, "type" appears to be treated as a normal bean property during deserialization instead of as a polymorphic discriminator.
I also observed equivalent failures for concrete message/content objects such as TextContent and AiMessage when they are stored directly in the AgenticScope state.
Source: langchain4j/langchain4j