Converting the result of a query's CompletableFuture in a REST endpoint

I feel stupid, but alas. If I have a REST endpoint and a query that returns a list of String, the code would look like this:

    @GetMapping
    public CompletableFuture<List<String>> list() {
    	return queryGateway.query(new GetActiveRosterDates(), ResponseTypes.multipleInstancesOf(String.class));
    }

But I’ve changed the key of the aggregate to LocalDate, so I’m getting a list of those, not String. And apparently those cannot be returned.

    @GetMapping
    public CompletableFuture<List<LocalDate>> list() {
    	return queryGateway.query(new GetActiveRosterDates(), ResponseTypes.multipleInstancesOf(LocalDate.class));
    }

Retrieved response [class java.util.ArrayList] is not convertible to a List of the expected response type [class java.time.LocalDate]

The response seems to need to return List of String, so we need to post process the query’s result. Since a toString can be pretty synchronous I’d expect this to work:

    @GetMapping
    public CompletableFuture<List<String>> list() {
    	return queryGateway.query(new GetActiveRosterDates(), ResponseTypes.multipleInstancesOf(LocalDate.class)) //
    		.thenApply(list -> list.stream().map(ld -> ld.toString()).toList());
    }

But apparently I’m wrong.

Retrieved response [class java.util.ArrayList] is not convertible to a List of the expected response type [class java.time.LocalDate]

Why?

Can you also share the @QueryHandler part?

KR,

The repo is open:
https://bitbucket.org/sftwrks/swxselfroster/src/main/src/main/java/nl/softworks/selfroster/projection/rosterDate/RosterDatesProjection.java

	private List<LocalDate> activeRosterDates = new ArrayList<>();
	@QueryHandler
	public List<LocalDate> query(GetActiveRosterDates query) {
		return activeRosterDates;
	}

public record GetActiveRosterDates() {}

I am assuming that you’re using the JacksonSerializer, @tbee.
If that’s the case, then I’d like to ask you to enable default typing on the JacksonSerializer (as is described here in the Reference Guide).

The problem you’re (I assume) facing is that the LocalDate serializes to a format that loses the knowledge it used to be a LocalDate. This isn’t an issue for singular objects, but when those are dropped in a collection, you might face problems.

So, let us know whether the default typing works!

Makes sense. However, if you’re using the spring based setup (like I am), when trying to configure the JacksonSerializer using spring @Configuration there is conflict with Axon’s configuration.

Parameter 0 of method messageSerializer in org.axonframework.springboot.autoconfig.AxonAutoConfiguration required a single bean, but 2 were found:
	- defaultSerializer: defined by method 'defaultSerializer' in class path resource [nl/softworks/selfroster/Configuration.class]
	- eventSerializer: defined by method 'eventSerializer' in class path resource [org/axonframework/springboot/autoconfig/AxonAutoConfiguration.class]

Alternatively using the Axon configuration class seems not to be picked up; there is nothing marking that class as configuration? At least the method I used for setting that up was never called.

So that is why I reverted to application.properties. However, there does not seem to be a property for defining “default typing” in that way, so I had dug deeper and found that “NON_CONCRETE_AND_ARRAYS” was set for DefaultTyping. But that cannot be set via that properties file, it seems. Allowed Jackson values in the property file are:

CLOSE_CLOSEABLE
EAGER_SERIALIZER_FETCH
FAIL_ON_EMPTY_BEANS
FAIL_ON_SELF_REFERENCES
FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS
FLUSH_AFTER_WRITE_VALUE
INDENT_OUTPUT
ORDER_MAP_ENTRIES_BY_KEYS
USE_EQUALITY_FOR_OBJECT_ID
WRAP_EXCEPTIONS
WRAP_ROOT_VALUE
WRITE_BIGDECIMAL_AS_PLAIN
WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS
WRITE_DATES_AS_TIMESTAMPS
WRITE_DATES_WITH_ZONE_ID
WRITE_DATE_KEYS_AS_TIMESTAMPS
WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS
WRITE_DURATIONS_AS_TIMESTAMPS
WRITE_EMPTY_JSON_ARRAYS
WRITE_ENUMS_USING_INDEX
WRITE_ENUMS_USING_TO_STRING
WRITE_ENUM_KEYS_USING_INDEX
WRITE_NULL_MAP_VALUES
WRITE_SELF_REFERENCES_AS_NULL
WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED

The @Primary annotation seems to solve the configuration conflicts.

Which indeed solved the serializer problem.