Serialization issue when using collection in event attributes

I got this error when I pass list of string in event

2022-10-12 14:25:03.571  WARN 61432 --- [ault-executor-0] o.a.c.gateway.DefaultCommandGateway      : Command 'app.aglet.backend.friends.commands.AnonymizePlayerCommand' resulted in org.axonframework.commandhandling.CommandExecutionException(No converter available
---- Debugging information ----
message             : No converter available
type                : java.util.ImmutableCollections$Set12
converter           : com.thoughtworks.xstream.converters.reflection.SerializableConverter
message[1]          : Unable to make private void java.util.ImmutableCollections$Set12.readObject(java.io.ObjectInputStream) throws java.io.IOException,java.lang.ClassNotFoundException accessible: module java.base does not "opens java.util" to unnamed module @4470fbd6
converter[1]        : com.thoughtworks.xstream.converters.reflection.ReflectionConverter
message[2]          : Unable to make field private final java.lang.Object java.util.ImmutableCollections$Set12.e0 accessible: module java.base does not "opens java.util" to unnamed module @4470fbd6
-------------------------------)

my saga code

@Saga
public class PlayerAnonymizingSaga {
    @Autowired
    private transient CommandGateway commandGateway;

    private Set<String> friends;

    @StartSaga
    @SagaEventHandler(associationProperty = "playerSagaId")
    public void handle(final AnonymizedPlayerEvent event) {
        String playerId = event.getPlayerId();
        String playerSagaId = event.getPlayerId();
        friends = new HashSet<>(event.getFriends());

        event.getFriends().forEach(friend -> {
            RemoveFriendshipWithMeCommand removeFriendshipWithMeCommand =
                    RemoveFriendshipWithMeCommand.builder()
                            .playerId(friend)
                            .playerSagaId(playerSagaId)
                            .friendId(playerId)
                            .build();
            commandGateway.send(removeFriendshipWithMeCommand);
        });
    }
}

event:


@Revision("1.0")
@Value
@AllArgsConstructor
public class AnonymizedPlayerEvent {
    private final String playerId;
    private final String playerSagaId;
    private final Set<String> friends;
}

Aggregate code:

    @CommandHandler
    public final void handle(final AnonymizePlayerCommand command) {
        String givenPlayerId = command.getPlayerId();
        apply(new AnonymizedPlayerEvent(givenPlayerId, givenPlayerId, Set.copyOf(friends)));
    }

How can I use collection in event without this serialization issue?
I used both latest and and 4.6.1 version of axoniq server docker container
ex. axoniq/axonserver:4.6.1-jdk-17-dev
Code is compiles and run using java 17 (eclipse temurin 17.0.4) and springboot

This predicament you’re facing is the fact XStream isn’t happy with JDK17.
More specifically, this started as of 1.4.18 of XStream, which the framework tackled in this pull request.
More specifically, you can read a warning in this section of the Reference Guide on the matter (copied for your convenience):

XStream and JDK 17
Although XStream can “serialize virtually anything,” more recent versions of the JDK impede its flexibility.
This predicament comes down to XStream’s reflective approach to finding out how to de-/serialize any object, which has become problematic with Java’s intent to secure its internals.
Hence, if you’re using JDK 17, the chances are that objects (e.g., your sagas) intended for serialization require additional configuration.

On some occasions configuring XStream’s security settings is sufficient.
Other times you will have to introduce custom Converters to de-/serialize specific types.
If you prefer not to deal with specific XStream settings, it might be better to use the JacksonSerializer throughout your Axon application.

Furthermore, you can actually spot this problem from the stack trace XStream provides, stating:

Unable to make field private final java.lang.Object java.util.ImmutableCollections$Set12.e0 accessible: module java.base does not “opens java.util” to unnamed module @4470fbd6

This is intentional, as JDK17 is closing a lot of the internal packages.
Most definitely those from any collection you’d use from the JDK itself.

The easiest solution you can take, @Arun, is switching your serializer from XStream to Jackson.

@Steven_van_Beelen Thanks for detailed response, it helps me understand the problem better.
I tried the exact code from the article. I got this error

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 1 of method eventSerializer in org.axonframework.springboot.autoconfig.AxonAutoConfiguration required a single bean, but 2 were found:
	- defaultSerializer: defined by method 'defaultSerializer' in class path resource [app/aglet/backend/friends/configuration/SerializerConfiguration.class]
	- messageSerializer: defined by method 'messageSerializer' in class path resource [app/aglet/backend/friends/configuration/SerializerConfiguration.class]


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

So I applied the suggested action by adding @Primary in the default serializer configuration


    @Primary
    @Bean
    public Serializer defaultSerializer() {
        return JacksonSerializer.defaultSerializer();
    }

    @Bean
    @Qualifier("messageSerializer")
    public Serializer messageSerializer() {
        return JacksonSerializer.defaultSerializer();
    }
    @Bean
    @Qualifier("eventSerializer")
    public Serializer eventSerializer() {
        return JacksonSerializer.defaultSerializer();
    }

Server started without any issue I am able to get some success, but while queries have

2022-10-17 23:12:33.271 ERROR 18471 --- [nio-8090-exec-2] .b.c.s.s.c.SupportConfig$LoggingNotifier : Error occurred: Retrieved response [class java.util.ArrayList] is not convertible to a List of the expected response type [class app.aglet.backend.friends.model.SentFriendshipRequest].

java.lang.IllegalArgumentException: Retrieved response [class java.util.ArrayList] is not convertible to a List of the expected response type [class app.aglet.backend.friends.model.SentFriendshipRequest]
	at org.axonframework.messaging.responsetypes.MultipleInstancesResponseType.convert(MultipleInstancesResponseType.java:180) ~[axon-messaging-4.6.1.jar:4.6.1]
	at org.axonframework.messaging.responsetypes.MultipleInstancesResponseType.convert(MultipleInstancesResponseType.java:47) ~[axon-messaging-4.6.1.jar:4.6.1]
	at org.axonframework.messaging.responsetypes.ConvertingResponseMessage.getPayload(ConvertingResponseMessage.java:102) ~[axon-messaging-4.6.1.jar:4.6.1]
	at org.axonframework.queryhandling.DefaultQueryGateway.lambda$query$1(DefaultQueryGateway.java:92) ~[axon-messaging-4.6.1.jar:4.6.1]
	at java.base/java.util.concurrent.CompletableFuture$UniAccept.tryFire$$$capture(CompletableFuture.java:718) ~[na:na]
	at java.base/java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java) ~[na:na]
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[na:na]
	at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2147) ~[na:na]
	at org.axonframework.axonserver.connector.query.AxonServerQueryBus$ResponseProcessingTask.run(AxonServerQueryBus.java:868) ~[axon-server-connector-4.6.1.jar:4.6.1]
	at org.axonframework.tracing.Span.run(Span.java:72) ~[axon-messaging-4.6.1.jar:4.6.1]
	at org.axonframework.tracing.Span.lambda$wrapRunnable$0(Span.java:90) ~[axon-messaging-4.6.1.jar:4.6.1]
	at org.axonframework.axonserver.connector.PriorityRunnable.run(PriorityRunnable.java:58) ~[axon-server-connector-4.6.1.jar:4.6.1]
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[na:na]
	at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

2022-10-17 23:12:33.271 DEBUG 18471 --- [nio-8090-exec-2] o.s.web.servlet.DispatcherServlet        : Unresolved failure from "ASYNC" dispatch: java.lang.IllegalArgumentException: Retrieved response [class java.util.ArrayList] is not convertible to a List of the expected response type [class app.aglet.backend.friends.model.SentFriendshipRequest]
2022-10-17 23:12:33.271 ERROR 18471 --- [nio-8090-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] threw exception

java.lang.IllegalArgumentException: Retrieved response [class java.util.ArrayList] is not convertible to a List of the expected response type [class app.aglet.backend.friends.model.SentFriendshipRequest]
	at org.axonframework.messaging.responsetypes.MultipleInstancesResponseType.convert(MultipleInstancesResponseType.java:180) ~[axon-messaging-4.6.1.jar:4.6.1]
	at org.axonframework.messaging.responsetypes.MultipleInstancesResponseType.convert(MultipleInstancesResponseType.java:47) ~[axon-messaging-4.6.1.jar:4.6.1]
	at org.axonframework.messaging.responsetypes.ConvertingResponseMessage.getPayload(ConvertingResponseMessage.java:102) ~[axon-messaging-4.6.1.jar:4.6.1]
	at org.axonframework.queryhandling.DefaultQueryGateway.lambda$query$1(DefaultQueryGateway.java:92) ~[axon-messaging-4.6.1.jar:4.6.1]
	at java.base/java.util.concurrent.CompletableFuture$UniAccept.tryFire$$$capture(CompletableFuture.java:718) ~[na:na]
	at java.base/java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java) ~[na:na]
	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[na:na]
	at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2147) ~[na:na]
	at org.axonframework.axonserver.connector.query.AxonServerQueryBus$ResponseProcessingTask.run(AxonServerQueryBus.java:868) ~[axon-server-connector-4.6.1.jar:4.6.1]
	at org.axonframework.tracing.Span.run(Span.java:72) ~[axon-messaging-4.6.1.jar:4.6.1]
	at org.axonframework.tracing.Span.lambda$wrapRunnable$0(Span.java:90) ~[axon-messaging-4.6.1.jar:4.6.1]
	at org.axonframework.axonserver.connector.PriorityRunnable.run(PriorityRunnable.java:58) ~[axon-server-connector-4.6.1.jar:4.6.1]
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[na:na]
	at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

Am I missing something?

code snippet

private CompletableFuture<List<FriendOutDto>> findAllFriendshipRequestsSentByPlayerId(
            final String playerId) {
        Function<SentFriendshipRequest, String> toRecipientUserId = sentFriendshipRequest ->
                sentFriendshipRequest.getId().getRecipientPlayerId();
        Function<User, FriendOutDto> toDto = FriendOutDto::new;

        return queryGateway.query(
                        new FindSentFriendshipRequestByPlayerByIdQuery(playerId),
                        multipleInstancesOf(SentFriendshipRequest.class))
                .thenApply(result -> Lists.transform(result, toRecipientUserId))
                .thenApply(result -> {
                    if (result.isEmpty()) {
                        return List.of();
                    }
                    return Lists.transform(userService.getById(result), toDto);
                });
    }

query handler

 @QueryHandler
    public List<SentFriendshipRequest> handle(final FindSentFriendshipRequestByPlayerByIdQuery query) {
        return repository.findAllByPlayerId(query.getId());
    }

This new issue you’ve hit has to do with Jackson not knowing the generic type inside the Collection.
You can either define default typing on the ObjectMapper, or add an annotation to the (in your sample) SentFriendshipRequest class to enforce the type is included in the JSON structure.

Note that default typing is not recommended by Jackson themselves, as there are potential security risks in taking that route.
Hence, I would recommend adding the annotation to your objects.
I believe the annotation to use is @JsonTypeInfo, by the way.

1 Like

Thank you @Steven_van_Beelen your suggestion helped me, though , I coudn’t resolve it using @JsonTypeInfo I transformed complicated object to simple dto, then all worked fine. Later I will spend some time to handle Entity objects properly.
Thank you so much

Happy to hear you’ve found a solution, @Arun! And, glad to help out :slight_smile: