[BUG] AgenticScopeSerializer cannot round-trip UserMessage stored directly in AgenticScope state

Author: gmunozfeCreated Sep 18, 2026Updated Sep 18, 2026
Labelsbug

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:

json
"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:

json
{
  "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:

java
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:

java
AgenticScopeSerializer.fromJson(json);

Expected behavior

Anything serialized successfully by:

java
AgenticScopeSerializer.toJson(scope)

should be deserializable by:

java
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.0
    • dev.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:

java
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:

java
scope.writeState(
        "candidate",
        new DefaultAgenticScope.AgentMessage(
                "agent",
                "agent$0",
                UserMessage.from("hello")));

The resulting JSON contains the polymorphic discriminator:

json
"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.