spring-cloud / spring-cloud-function

Apache License 2.0
1.04k stars 618 forks source link

ClassCastException when parsing json payload with nested classes with interfaces #1191

Closed ilopmar closed 1 month ago

ilopmar commented 1 month ago

Describe the bug

In my project we receive json messages through Kafka that are deserialized automatically to Java objects. Those java objects are created automatically using Async API Spec.

We have upgraded org.springframework.cloud:spring-cloud-dependencies (SCD) from 2023.0.1 to 2023.0.3 and we found one issue deserializing some messages.

I've tracked down the problem to spring-cloud-function-context:4.1.3 (included in SCD 2023.0.3). Forcing that version to the previous one 4.1.2 makes it work again.

This is the stacktrace:

org.springframework.kafka.listener.ListenerExecutionFailedException: Listener failed
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.decorateException(KafkaMessageListenerContainer.java:2961) ~[spring-kafka-3.1.9.jar:3.1.9]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeOnMessage(KafkaMessageListenerContainer.java:2902) ~[spring-kafka-3.1.9.jar:3.1.9]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeOnMessage(KafkaMessageListenerContainer.java:2866) ~[spring-kafka-3.1.9.jar:3.1.9]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.lambda$doInvokeRecordListener$55(KafkaMessageListenerContainer.java:2783) ~[spring-kafka-3.1.9.jar:3.1.9]
    at io.micrometer.observation.Observation.observe(Observation.java:565) ~[micrometer-observation-1.12.10.jar:1.12.10]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeRecordListener(KafkaMessageListenerContainer.java:2781) ~[spring-kafka-3.1.9.jar:3.1.9]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeWithRecords(KafkaMessageListenerContainer.java:2631) ~[spring-kafka-3.1.9.jar:3.1.9]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeRecordListener(KafkaMessageListenerContainer.java:2517) ~[spring-kafka-3.1.9.jar:3.1.9]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeListener(KafkaMessageListenerContainer.java:2155) ~[spring-kafka-3.1.9.jar:3.1.9]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeIfHaveRecords(KafkaMessageListenerContainer.java:1495) ~[spring-kafka-3.1.9.jar:3.1.9]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.pollAndInvoke(KafkaMessageListenerContainer.java:1460) ~[spring-kafka-3.1.9.jar:3.1.9]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.run(KafkaMessageListenerContainer.java:1330) ~[spring-kafka-3.1.9.jar:3.1.9]
    at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1804) ~[na:na]
    at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
Caused by: org.springframework.kafka.KafkaException: Failed to execute runnable
    at org.springframework.integration.kafka.inbound.KafkaInboundEndpoint.doWithRetry(KafkaInboundEndpoint.java:82) ~[spring-integration-kafka-6.2.9.jar:6.2.9]
    at org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter$IntegrationRecordMessageListener.onMessage(KafkaMessageDrivenChannelAdapter.java:457) ~[spring-integration-kafka-6.2.9.jar:6.2.9]
    at org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter$IntegrationRecordMessageListener.onMessage(KafkaMessageDrivenChannelAdapter.java:422) ~[spring-integration-kafka-6.2.9.jar:6.2.9]
    at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeOnMessage(KafkaMessageListenerContainer.java:2888) ~[spring-kafka-3.1.9.jar:3.1.9]
    ... 12 common frames omitted
Caused by: org.springframework.messaging.MessageHandlingException: error occurred in message handler [org.springframework.cloud.stream.function.FunctionConfiguration$FunctionToDestinationBinder$1@3fe9c146]
    at org.springframework.integration.support.utils.IntegrationUtils.wrapInHandlingExceptionIfNecessary(IntegrationUtils.java:191) ~[spring-integration-core-6.2.9.jar:6.2.9]
    at org.springframework.integration.handler.AbstractMessageHandler.doHandleMessage(AbstractMessageHandler.java:108) ~[spring-integration-core-6.2.9.jar:6.2.9]
    at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:73) ~[spring-integration-core-6.2.9.jar:6.2.9]
    at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:132) ~[spring-integration-core-6.2.9.jar:6.2.9]
    at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:133) ~[spring-integration-core-6.2.9.jar:6.2.9]
    at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:106) ~[spring-integration-core-6.2.9.jar:6.2.9]
    at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:72) ~[spring-integration-core-6.2.9.jar:6.2.9]
    at org.springframework.integration.channel.AbstractMessageChannel.sendInternal(AbstractMessageChannel.java:390) ~[spring-integration-core-6.2.9.jar:6.2.9]
    at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:334) ~[spring-integration-core-6.2.9.jar:6.2.9]
    at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:304) ~[spring-integration-core-6.2.9.jar:6.2.9]
    at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:187) ~[spring-messaging-6.1.13.jar:6.1.13]
    at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:166) ~[spring-messaging-6.1.13.jar:6.1.13]
    at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:47) ~[spring-messaging-6.1.13.jar:6.1.13]
    at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:109) ~[spring-messaging-6.1.13.jar:6.1.13]
    at org.springframework.integration.endpoint.MessageProducerSupport.lambda$sendMessage$1(MessageProducerSupport.java:262) ~[spring-integration-core-6.2.9.jar:6.2.9]
    at io.micrometer.observation.Observation.observe(Observation.java:499) ~[micrometer-observation-1.12.10.jar:1.12.10]
    at org.springframework.integration.endpoint.MessageProducerSupport.sendMessage(MessageProducerSupport.java:262) ~[spring-integration-core-6.2.9.jar:6.2.9]
    at org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter.sendMessageIfAny(KafkaMessageDrivenChannelAdapter.java:391) ~[spring-integration-kafka-6.2.9.jar:6.2.9]
    at org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter$IntegrationRecordMessageListener.lambda$onMessage$0(KafkaMessageDrivenChannelAdapter.java:460) ~[spring-integration-kafka-6.2.9.jar:6.2.9]
    at org.springframework.integration.kafka.inbound.KafkaInboundEndpoint.lambda$doWithRetry$0(KafkaInboundEndpoint.java:77) ~[spring-integration-kafka-6.2.9.jar:6.2.9]
    at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:344) ~[spring-retry-2.0.9.jar:na]
    at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:233) ~[spring-retry-2.0.9.jar:na]
    at org.springframework.integration.kafka.inbound.KafkaInboundEndpoint.doWithRetry(KafkaInboundEndpoint.java:70) ~[spring-integration-kafka-6.2.9.jar:6.2.9]
    ... 15 common frames omitted
Caused by: java.lang.ClassCastException: class [B cannot be cast to class com.example.issue.dto.ReleaseTopicMessageDTO$Payload ([B is in module java.base of loader 'bootstrap'; com.example.issue.dto.ReleaseTopicMessageDTO$Payload is in unnamed module of loader 'app')
    at com.example.issue.ReleaseEventHandler.accept(ReleaseEventHandler.java:7) ~[main/:na]
    at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.invokeConsumer(SimpleFunctionRegistry.java:1063) ~[spring-cloud-function-context-4.1.3.jar:4.1.3]
    at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.doApply(SimpleFunctionRegistry.java:761) ~[spring-cloud-function-context-4.1.3.jar:4.1.3]
    at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.apply(SimpleFunctionRegistry.java:592) ~[spring-cloud-function-context-4.1.3.jar:4.1.3]
    at org.springframework.cloud.stream.function.PartitionAwareFunctionWrapper.apply(PartitionAwareFunctionWrapper.java:92) ~[spring-cloud-stream-4.1.3.jar:4.1.3]
    at org.springframework.cloud.stream.function.FunctionConfiguration$FunctionWrapper.apply(FunctionConfiguration.java:823) ~[spring-cloud-stream-4.1.3.jar:4.1.3]
    at org.springframework.cloud.stream.function.FunctionConfiguration$FunctionToDestinationBinder$1.handleMessageInternal(FunctionConfiguration.java:654) ~[spring-cloud-stream-4.1.3.jar:4.1.3]
    at org.springframework.integration.handler.AbstractMessageHandler.doHandleMessage(AbstractMessageHandler.java:105) ~[spring-integration-core-6.2.9.jar:6.2.9]
    ... 36 common frames omitted

Sample

There is a sample project with the detailed explanation and all the steps necessary to reproduce the issue in https://github.com/ilopmar/issue-spring-cloud-function-context. Please follow the instructions on the README file to reproduce the issue with 2023.0.3 and then to make it work with 2023.0.2

olegz commented 1 month ago

This issue has been addressed in 4.1.4-SNAPSHOT. So make your <spring-cloud.version>2023.0.4-SNAPSHOT</spring-cloud.version> and you should be fine. Just tested it. You can verify it with this simple code which indeed fails in 2023.0.3 and not 2023.0.2. But then again succeeds with 2023.0.4-SNAPSHOT

ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
String json = "{\n"
        + "  \"type\": \"OVA\",\n"
        + "  \"ova\": {\n"
        + "    \"image\": {\n"
        + "      \"image_type\": \"IMAGE_URI\",\n"
        + "      \"location\": \"s3://xxxxxxxxx/bitnami-neo4j-5.24.2-r0-debian-12-amd64.ova\"\n"
        + "    }\n"
        + "  },\n"
        + "  \"emitted_on\": \"2024-10-16T09:59:03.561548427Z\"\n"
        + "}";
JsonMapper mapper = context.getBean(JsonMapper.class);
System.out.println((Payload) mapper.fromJson(json, Payload.class));