Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ClassCastException after upgrade to Spring Boot 2.5-RC1 #3558

Closed
agebhar1 opened this issue Apr 29, 2021 · 26 comments · Fixed by #3561
Closed

ClassCastException after upgrade to Spring Boot 2.5-RC1 #3558

agebhar1 opened this issue Apr 29, 2021 · 26 comments · Fixed by #3561
Assignees
Milestone

Comments

@agebhar1
Copy link
Contributor

In what version(s) of Spring Integration are you seeing this issue?

5.5.0-RC1 via Spring Boot 2.5-RC1

Describe the bug

java.lang.ClassCastException: java.lang.String cannot be cast to org.springframework.messaging.Message
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_292]
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_292]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_292]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_292]
        at org.springframework.integration.handler.LambdaMessageProcessor.processMessage(LambdaMessageProcessor.java:97) ~[spring-integration-core-5.5.0-RC1.jar:5.5.0-RC1]
        at org.springframework.integration.handler.ServiceActivatingHandler.handleRequestMessage(ServiceActivatingHandler.java:105) [spring-integration-core-5.5.0-RC1.jar:5.5.0-RC1]
        at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:134) [spring-integration-core-5.5.0-RC1.jar:5.5.0-RC1]

on handler method within integration flow.

Sample

https://github.com/agebhar1/spring-integration-kotlin-bug

The error does not occur if explicitly use Koltin 1.4(.32)

@agebhar1 agebhar1 added status: waiting-for-triage The issue need to be evaluated and its future decided type: bug labels Apr 29, 2021
@artembilan
Copy link
Member

Well, isn't that a problem in Kotlin then, rather than in Spring Integration?
Looks like Spring Boot exposes this one as a version for us kotlinVersion=1.5.0-RC.

In Spring Integration we have:

ext.kotlinVersion = '1.4.32'
...
compileKotlin {
		kotlinOptions {
			languageVersion = '1.3'
			jvmTarget = '1.8'
			freeCompilerArgs = ['-Xjsr305=strict']
			allWarningsAsErrors = true
		}
}

This is exactly aligned with Spring Framework: https://github.com/spring-projects/spring-framework/blob/main/build.gradle#L4.

Meanwhile I'm adding your test into the project for more coverage.
And probably I'll try to compile it with that kotlinVersion=1.5.0-RC...

BTW, haven't you tried with the latest Kotlin SNAPSHOT? Might be very the case that there is something was fixed around reflection...

@artembilan artembilan added status: waiting-for-reporter Needs a feedback from the reporter and removed status: waiting-for-triage The issue need to be evaluated and its future decided labels Apr 29, 2021
@artembilan
Copy link
Member

While copying your sample, I'd suggest you take a look into a Kotlin DSL we provide in Spring Integration: https://docs.spring.io/spring-integration/docs/current/reference/html/kotlin-dsl.html#kotlin-dsl

@agebhar1
Copy link
Contributor Author

Thanks @artembilan for your fast reply. I also think the problem is in Kotlin 1.5-RC. The dependency to Kotlin 1.5-RC comes from Spring Boot spring-boot-dependencies-2.5.0-RC1

<kotlin.version>1.5.0-RC</kotlin.version>

Yes I tried the latest SNAPSHOT (removed the local .m2 directory).

Thank you for the hint to the Kotlin DSL.

@artembilan
Copy link
Member

Well, this one .handle { message: Message<String>, _ -> message.also { println(it.payload) } } indeed leads to that ClassCastException. See docs: https://docs.spring.io/spring-integration/docs/current/reference/html/dsl.html#java-dsl-class-cast.

So, it's probably a bug in the current Kotlin 1.4.x since we haven't caught it before.
What I see from debug that this Kotlin lambda is not a Java lambda and we end up in the else block:

		if (ClassUtils.isLambda(handler.getClass())) {
			serviceActivatingHandler = new ServiceActivatingHandler(new LambdaMessageProcessor(handler, payloadType));
		}
		else {
			serviceActivatingHandler = new ServiceActivatingHandler(handler, ClassUtils.HANDLER_HANDLE_METHOD);
		}

Therefore something indeed was changed in Kotlin 1.5 to make us happy in Java world.
Try this one as a recommended fix for this kind of use-cases:

.handle(Message::class.java) { message, _ -> message.also { println(it.payload) } }

I'm still looking into Kotlin 1.5 😄

@agebhar1
Copy link
Contributor Author

Oh 😱 - thanks a lot for the suggested fix 👍

@agebhar1
Copy link
Contributor Author

agebhar1 commented Apr 29, 2021

With the transformer part it's the same problem:

	public <P, T> B transform(Class<P> payloadType, GenericTransformer<P, T> genericTransformer,
			Consumer<GenericEndpointSpec<MessageTransformingHandler>> endpointConfigurer) {

		Assert.notNull(genericTransformer, "'genericTransformer' must not be null");
		Transformer transformer = genericTransformer instanceof Transformer ? (Transformer) genericTransformer :
				(ClassUtils.isLambda(genericTransformer.getClass())
						? new MethodInvokingTransformer(new LambdaMessageProcessor(genericTransformer, payloadType))
						: new MethodInvokingTransformer(genericTransformer, ClassUtils.TRANSFORMER_TRANSFORM_METHOD));
		return addComponent(transformer)
				.handle(new MessageTransformingHandler(transformer), endpointConfigurer);
	}

@artembilan
Copy link
Member

So, yes, something has changed in Kotlin 1.5 and we have true now for a Kotlin class being lambda:

class io.github.agebhar1.SpringIntegrationKotlinTests$Configuration$$Lambda$688/0x0000000800eb7840
...
public static boolean isLambda(Class<?> aClass) {
	return aClass.isSynthetic() && !aClass.isAnonymousClass() && !aClass.isLocalClass();
}

Therefore the fix I suggest with an extra type argument for Message::class.java is the way to go.
And since we have that covered in the docs, I'm inclined to close this issue as Works as Designed.

Your transformer logic to just copy headers is redundant: if you return just a payload, the framework will create a message for you and copy request headers, respectively.

@agebhar1
Copy link
Contributor Author

Okay to fix the test with Kotlin 1.5-RC is to change the flow like this:

        @Bean
        fun flow(): StandardIntegrationFlow =
                from(input())
                        .handle(Message::class.java) { message, _ -> message.also { println(it.payload) } }
                        .transform(object : AbstractTransformer() {
                            override fun doTransform(message: Message<*>): Any {
                                return MessageBuilder.withPayload((message.payload as String).toUpperCase())
                                        .copyHeaders(message.headers)
                                        .build()
                            }
                        })
                        .channel(output())
                        .get()

The header in the transform part is used in the real code.

I'm fine with closing the issue. Maybe some hint should be added in either the Spring Boot or Spring Integration upgrade documentation.

Thank you!

@artembilan
Copy link
Member

Thanks for confirmation!

I don't think there is needed some hint, since we have that doc already pointing for the reason of such a ClassCastException.
Plus, it is indeed recommended to use an idiomatic Kotlin DSL instead. 😄

@artembilan artembilan added status: invalid Not reproducable or not relevant to the current state of the project and removed status: waiting-for-reporter Needs a feedback from the reporter type: bug labels Apr 29, 2021
@agebhar1
Copy link
Contributor Author

Unfortunately with the Kotlin DSL this flow also raise an ClassCastException:

@Bean
fun flow() =
    integrationFlow(input()) {
        handle<Message<String>> { message, _ -> message.also { println(it.payload) } }
        transform<Message<String>> { message -> MessageBuilder.withPayload(message.payload.uppercase()).copyHeaders(message.headers) }
        channel(output())
    }

@artembilan
Copy link
Member

And this one indeed a problem.
We need to figure out what to do with that... Looks like we can't figure out an input type and fallback to regular payload propagation.

It fails even with current Kotlin 1.4.x support:

org.springframework.messaging.MessageHandlingException: error occurred during processing message in 'MethodInvokingMessageProcessor' [org.springframework.integration.handler.MethodInvokingMessageProcessor@3db432c2]; nested exception is java.lang.ClassCastException: class java.lang.String cannot be cast to class org.springframework.messaging.Message (java.lang.String is in module java.base of loader 'bootstrap'; org.springframework.messaging.Message is in unnamed module of loader 'app')
, failedMessage=GenericMessage [payload=test, headers={id=973e6698-e60f-e56b-4f47-f0b054782e2c, timestamp=1619723749562}]
	at org.springframework.integration.support.utils.IntegrationUtils.wrapInHandlingExceptionIfNecessary(IntegrationUtils.java:192)
	at org.springframework.integration.handler.MethodInvokingMessageProcessor.processMessage(MethodInvokingMessageProcessor.java:111)
	at org.springframework.integration.handler.ServiceActivatingHandler.handleRequestMessage(ServiceActivatingHandler.java:105)
	at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:134)
	at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:56)
	at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:115)
	at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:133)
	at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:106)
	at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:72)
	at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:317)
	at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:272)
	at org.springframework.integration.dsl.KotlinDslTests.no reply from handle(KotlinDslTests.kt:221)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1510)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1510)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:221)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: java.lang.ClassCastException: class java.lang.String cannot be cast to class org.springframework.messaging.Message (java.lang.String is in module java.base of loader 'bootstrap'; org.springframework.messaging.Message is in unnamed module of loader 'app')
	at org.springframework.integration.dsl.KotlinDslTests$no reply from handle$integrationFlow$1$$special$$inlined$handle$1.handle(KotlinIntegrationFlowDefinition.kt:456)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:171)
	at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:120)
	at org.springframework.integration.handler.support.MessagingMethodInvokerHelper$HandlerMethod.invoke(MessagingMethodInvokerHelper.java:1101)
	at org.springframework.integration.handler.support.MessagingMethodInvokerHelper.invokeHandlerMethod(MessagingMethodInvokerHelper.java:583)
	at org.springframework.integration.handler.support.MessagingMethodInvokerHelper.processInternal(MessagingMethodInvokerHelper.java:478)
	at org.springframework.integration.handler.support.MessagingMethodInvokerHelper.process(MessagingMethodInvokerHelper.java:356)
	at org.springframework.integration.handler.MethodInvokingMessageProcessor.processMessage(MethodInvokingMessageProcessor.java:108)
	... 75 more

Thank you for your persistence!

Reopening...

@artembilan artembilan reopened this Apr 29, 2021
@artembilan artembilan added status: waiting-for-triage The issue need to be evaluated and its future decided and removed status: invalid Not reproducable or not relevant to the current state of the project labels Apr 29, 2021
@agebhar1
Copy link
Contributor Author

I will have a look on weekend. Let me know if I can help.

@artembilan
Copy link
Member

Thank you, @agebhar1 , for your proposal!

For now it is hard to say how you can help...

I'm thinking about removing crossinline from those inline functions in the KotlinIntegrationFlowDefinition, just because a stack trace doesn't give us a clue where is the problem in the end-user code:

at org.springframework.integration.dsl.KotlinDslTests$no reply from handle$integrationFlow$1$$special$$inlined$handle$1.handle(KotlinIntegrationFlowDefinition.kt:456)

The maximum what we have, but this is still not an end-user line of code. So, if there is really some end-user mistake in that code, he (she) won't have a clue where to go.

With a noinline instead I got something like this:

	at org.springframework.integration.dsl.KotlinDslTests$no reply from handle$integrationFlow$1$1.invoke(KotlinDslTests.kt:216)
	at org.springframework.integration.dsl.KotlinDslTests$no reply from handle$integrationFlow$1$inlined$sam$i$org_springframework_integration_handler_GenericHandler$0.handle(KotlinIntegrationFlowDefinition.kt)

So, we have that KotlinDslTests.kt:216 to investigate for ourselves.

Re. casting problem... Let's see if we can assume that class name containing that $inlined$sam$ is a lambda in the ClassUtils.isLambda() and let it pass to the LambdaMessageProcessor!

@agebhar1
Copy link
Contributor Author

agebhar1 commented May 1, 2021

The minimal flow w/ Kotlin DSL with only the handle part I've tried is:

@Bean
fun flow(): IntegrationFlow {
    return integrationFlow(input()) {
        handle<Message<String>> { message, _ -> message.also { println(it) } }
        channel(output())
    }
}

The de-compiled Java version by IntelliJ (stripped garbage) is:

@Bean
@NotNull
public IntegrationFlow flow() {

    return IntegrationFlowDslKt.integrationFlow(input(), new Function1() {

        public Object invoke(Object var1) {
            this.invoke((KotlinIntegrationFlowDefinition) var1);
            return Unit.INSTANCE;
        }

        public final void invoke(@NotNull KotlinIntegrationFlowDefinition flow) {
            flow.getDelegate().handle(Message.class, new SpringIntegrationKotlinTests$Configuration$flow$1$$special$$inlined$handle$1());
            flow.channel(output());
        }
    });
}

with

final class SpringIntegrationKotlinTests$Configuration$flow$1$$special$$inlined$handle$1 implements GenericHandler {
    @NotNull
    public final Object handle(Object p, MessageHeaders h) {
        Message message = (Message) p;
        System.out.println(message);
        return message;
    }
}

A pure Java integration flow is:

@Bean
public IntegrationFlow flow() {
    return from(input())
            .handle(Message.class, new SpringIntegrationKotlinTests$Configuration$flow$1$$special$$inlined$handle$1())
            .channel(output())
            .get();
}

The test also fails with:

org.springframework.messaging.MessageHandlingException: error occurred during processing message in 'MethodInvokingMessageProcessor' [org.springframework.integration.handler.MethodInvokingMessageProcessor@eb507b9]; nested exception is java.lang.ClassCastException: class java.lang.String cannot be cast to class org.springframework.messaging.Message (java.lang.String is in module java.base of loader 'bootstrap'; org.springframework.messaging.Message is in unnamed module of loader 'app')
, failedMessage=GenericMessage [payload=hello, headers={id=ca81cd33-91bc-45d6-ac3c-bd44f482f018, timestamp=1619888579242}]
	at org.springframework.integration.support.utils.IntegrationUtils.wrapInHandlingExceptionIfNecessary(IntegrationUtils.java:192)
	at org.springframework.integration.handler.MethodInvokingMessageProcessor.processMessage(MethodInvokingMessageProcessor.java:111)
	at org.springframework.integration.handler.ServiceActivatingHandler.handleRequestMessage(ServiceActivatingHandler.java:105)
	at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:134)
	at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:56)
	at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:115)
	at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:133)
	at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:106)
	at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:72)
	at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:317)
	at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:272)
	at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:187)
	at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:166)
	at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:47)
	at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:109)
	at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:99)
	at io.github.agebhar1.SpringIntegrationJavaTests.shouldTransformToUpperCase(SpringIntegrationJavaTests.java:40)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:221)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: java.lang.ClassCastException: class java.lang.String cannot be cast to class org.springframework.messaging.Message (java.lang.String is in module java.base of loader 'bootstrap'; org.springframework.messaging.Message is in unnamed module of loader 'app')
	at io.github.agebhar1.SpringIntegrationKotlinTests$Configuration$flow$1$$special$$inlined$handle$1.handle(SpringIntegrationJavaTests.java:28)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:171)
	at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:120)
	at org.springframework.integration.handler.support.MessagingMethodInvokerHelper$HandlerMethod.invoke(MessagingMethodInvokerHelper.java:1105)
	at org.springframework.integration.handler.support.MessagingMethodInvokerHelper.invokeHandlerMethod(MessagingMethodInvokerHelper.java:583)
	at org.springframework.integration.handler.support.MessagingMethodInvokerHelper.processInternal(MessagingMethodInvokerHelper.java:478)
	at org.springframework.integration.handler.support.MessagingMethodInvokerHelper.process(MessagingMethodInvokerHelper.java:356)
	at org.springframework.integration.handler.MethodInvokingMessageProcessor.processMessage(MethodInvokingMessageProcessor.java:108)
	... 80 more

But if the implementing class of GenericHandler provides type information the test passes:

final class SpringIntegrationKotlinTests$Configuration$flow$1$$special$$inlined$handle$1 implements GenericHandler<Message<String>> {
    @Override
    public Object handle(Message<String> payload, MessageHeaders headers) {
        System.out.println(payload);
        return payload;
    }
}

I can provide the tests in the repository if it's helpful.

@agebhar1
Copy link
Contributor Author

agebhar1 commented May 1, 2021

This is invalid Kotlin code:

@Bean
fun flow(): IntegrationFlow {
    return integrationFlow(input()) {
        handle<Message<String>> (GenericHandler<Message<String>> { payload, _ -> payload.also { println(it) } })
        channel(output())
    }
}

the compiler complains:

[ERROR] Failed to execute goal org.jetbrains.kotlin:kotlin-maven-plugin:1.4.32:test-compile (test-compile) on project spring-integration-kotlin-bug-example: Compilation failure
[ERROR] /home/agebhar1/src/github.com/agebhar1/spring-integration-kotlin-bug/src/test/kotlin/io/github/agebhar1/SpringIntegrationKotlinTests.kt:[57,17] None of the following functions can be called with the arguments supplied: 
[ERROR] public final inline fun <reified P> handle(crossinline handler: (TypeVariable(P), MessageHeaders) -> Any): Unit defined in org.springframework.integration.dsl.KotlinIntegrationFlowDefinition
[ERROR] public final fun handle(messageHandler: (Message<*>) -> Unit): Unit defined in org.springframework.integration.dsl.KotlinIntegrationFlowDefinition
[ERROR] public final fun <H : MessageHandler> handle(messageHandler: TypeVariable(H), endpointConfigurer: GenericEndpointSpec<TypeVariable(H)>.() -> Unit = ...): Unit defined in org.springframework.integration.dsl.KotlinIntegrationFlowDefinition
[ERROR] public final fun handle(service: Any, methodName: String? = ...): Unit defined in org.springframework.integration.dsl.KotlinIntegrationFlowDefinition
[ERROR] public final fun handle(beanName: String, methodName: String? = ...): Unit defined in org.springframework.integration.dsl.KotlinIntegrationFlowDefinition
[ERROR] public final fun <H : MessageHandler?> handle(messageHandlerSpec: MessageHandlerSpec<*, TypeVariable(H)>): Unit defined in org.springframework.integration.dsl.KotlinIntegrationFlowDefinition
[ERROR] public final fun <H : MessageHandler> handle(messageHandlerSpec: MessageHandlerSpec<*, TypeVariable(H)>, endpointConfigurer: GenericEndpointSpec<TypeVariable(H)>.() -> Unit = ...): Unit defined in org.springframework.integration.dsl.KotlinIntegrationFlowDefinition
[ERROR] public final fun handle(messageProcessorSpec: MessageProcessorSpec<*>, endpointConfigurer: GenericEndpointSpec<ServiceActivatingHandler>.() -> Unit = ...): Unit defined in org.springframework.integration.dsl.KotlinIntegrationFlowDefinition
[ERROR] public final fun handle(messageHandler: MessageHandler): Unit defined in org.springframework.integration.dsl.KotlinIntegrationFlowDefinition

@agebhar1
Copy link
Contributor Author

agebhar1 commented May 2, 2021

As far as I can see, the org.springframework.messaging.handler.invocation.InvocableHandlerMethod has not enough runtime information to handle the first argument for GenericHandler<T> right.

raw interface use like Kotlins compiler:
raw-interface

typed interface use:
typed-interface

The provided class information in IntegrationsFlow.handle is not taken into account like in the lambda version.

@artembilan
Copy link
Member

Thank you, @agebhar1 , for sharing those reports!
It is really helpful info to know how is that decompiled.

What I see in our tests that it works for transform(), route(), split(). For any those which comes with a single lambda argument.
The GenericHandler problem that it is essentially a BiFunction and we don't see a generic info for that at runtime.
The ResolvableType.forMethodParameter(this, null, 1).resolve() doesn't see that refined type for GenericHandler somehow.
It is OK for those who are plain Function contract: GenericTransformer, GenericSelector, route() expects Function, as well as split(). So, the problem is only with a GenericHandler reflection info somehow missed somewhere deep in Java. 😢

Not sure what to do yet...

@artembilan
Copy link
Member

Well, I played a little bit and in the KotlinIntegrationFlowDefinition did change for the handle() method like this:

inline fun <reified P> handle(crossinline handler: (P, MessageHeaders) -> Any) {
	this.delegate.transform(P::class.java) { p -> handler(p, MessageHeaders(null)) }
}

Leaving our end-user code as it is: handle<Message<String>> { message, _ -> payloadReference.set(message.payload) }
I delegate to transform() which comes with only one method arg.
I ended up with an exception like:

org.springframework.integration.handler.ReplyRequiredException: No reply produced by handler 'org.springframework.integration.dsl.StandardIntegrationFlow#0.transformer#0', and its 'requiresReply' property is set to true.

Which confirms that we enter an end-user code with desired Message object as an input.

This also confirms that generic info is carried correctly only when one param is present in the method final method signature.

Other notes: noinline is not good. Even the single param lambdas are failing without appropriate generic info...

So, I'm not saying that Kotlin is wrong somewhere, but definitely something to investigate.

@agebhar1
Copy link
Contributor Author

agebhar1 commented May 4, 2021

I had a similar idea, in

public <P> B handle(Class<P> payloadType, GenericHandler<P> handler,
Consumer<GenericEndpointSpec<ServiceActivatingHandler>> endpointConfigurer) {
ServiceActivatingHandler serviceActivatingHandler;
if (ClassUtils.isLambda(handler.getClass())) {
serviceActivatingHandler = new ServiceActivatingHandler(new LambdaMessageProcessor(handler, payloadType));
}
else {
serviceActivatingHandler = new ServiceActivatingHandler(handler, ClassUtils.HANDLER_HANDLE_METHOD);
}
return handle(serviceActivatingHandler, endpointConfigurer);
}
if the handler is not a lambda we can check if the type parameter is present for it. If it's not present we can wrap the handler in an explicit Java GenericHandler<T> instance to provide the required parameter information. The compiler ensures it's a valid interface implementation of GenericHandler<T> with Class<T>.

This could solve the problem for Kotlin as well as for Java with using raw GenericHandler.

What did you think?

@artembilan
Copy link
Member

Sorry, I'm not sure what you mean. This doesn't work:

	else {
			serviceActivatingHandler =
					new ServiceActivatingHandler(new GenericHandler<P>() {

						@Override
						public Object handle(P payload, MessageHeaders headers) {
							return handler.handle(payload, headers);
						}
					}, ClassUtils.HANDLER_HANDLE_METHOD);
		}

Just because it is Java and that generic type is erased during compilation we still end up with an Object for the first param and therefore same ClassCast on the final end-user Kotlin Lambda.

Would you mind to share the change you think of?

Thank you for your help on this!

@agebhar1
Copy link
Contributor Author

agebhar1 commented May 4, 2021

You are totally right. I missed the point that it's library code and not custom code. But let's think the other way. If the handler is not detected as a lambda and no runtime type information is available, like an instance of raw GenericHandler or Kotlins compiled one. In this case we create a lambda and delegate to the provided handler.

lambda-fails

lambda-passes

I am happy to help.

@artembilan
Copy link
Member

Thank you for your help!
I'm still not sure in your fix, but this is what I have figured out so far:

	public <P> B handle(Class<P> payloadType, GenericHandler<P> handler,
			Consumer<GenericEndpointSpec<ServiceActivatingHandler>> endpointConfigurer) {

		ServiceActivatingHandler serviceActivatingHandler;
		if (ClassUtils.isLambda(handler.getClass())) {
			serviceActivatingHandler = new ServiceActivatingHandler(new LambdaMessageProcessor(handler, payloadType));
		}
		else if (payloadType != null) {
			return handle(payloadType, handler::handle, endpointConfigurer);
		}
		else {
			serviceActivatingHandler = new ServiceActivatingHandler(handler, ClassUtils.HANDLER_HANDLE_METHOD);
		}
		return handle(serviceActivatingHandler, endpointConfigurer);
	}

Pay attention to the else if (payloadType != null) { block.
It doesn't reflect yours " If it's not present", because I do exactly opposite: when type is present and GenericHandler is not a lambda, I wrap it into a lambda to carry that type info properly into a recursive handle() call.

If this is OK with you, I'm ready to PR the fix!

@agebhar1
Copy link
Contributor Author

agebhar1 commented May 4, 2021

It's similar to what I had in mind but simpler. The delegation by the method reference does the trick to create the provide the required runtime type information.

else if (payloadType != null) {
	return handle(payloadType, handler::handle, endpointConfigurer);
}

This is a compact form of the lambda I created in the code above. You also simplified (skip) the check "if it's not present" type information.

I'm fine with this fix 👍

@artembilan
Copy link
Member

I compared this one:

@FunctionalInterface
public interface GenericSelector<S> {

	boolean accept(S source);

}

and this:

@FunctionalInterface
public interface GenericHandler<P> {

	Object handle(P payload, MessageHeaders headers);

}

Using this generic info accessor:

handler.getClass().getGenericInterfaces()

And this is what I got for the:

filter<Message<*>> { m -> m.payload is String }
result = {Type[1]@7232} 
 0 = {ParameterizedTypeImpl@7233} "org.springframework.integration.core.GenericSelector<org.springframework.messaging.Message<?>>"
  actualTypeArguments = {Type[1]@7237} 
   0 = {ParameterizedTypeImpl@7239} "org.springframework.messaging.Message<?>"
  rawType = {Class@4773} "interface org.springframework.integration.core.GenericSelector"
  ownerType = null

And this is what for the:

	handle<Message<String>> { message, _ -> payloadReference.set(message.payload) }
result = {Type[1]@7541} 
 0 = {ParameterizedTypeImpl@7542} "org.springframework.integration.handler.GenericHandler<P>"
  actualTypeArguments = {Type[1]@7546} 
   0 = {TypeVariableImpl@7548} "P"
  rawType = {Class@4774} "interface org.springframework.integration.handler.GenericHandler"
  ownerType = null

Then I checked similar structure with Java:

			.handle(String.class, new GenericHandler<String>() {

								@Override public Object handle(String p, MessageHeaders h) {
									throw new RuntimeException("intentional");
								}
							},
							e -> e.advice(retryAdvice()))

(Right, anonymous because of generic info presence) and I get this result:

result = {Type[1]@5867} 
 0 = {ParameterizedTypeImpl@5868} "org.springframework.integration.handler.GenericHandler<java.lang.String>"
  actualTypeArguments = {Type[1]@5873} 
   0 = {Class@606} "class java.lang.String"
  rawType = {Class@4125} "interface org.springframework.integration.handler.GenericHandler"
  ownerType = null

So, looks like indeed Kotlin is missing that generic info for two-param methods 😢 .

Will PR the fix we have talked tomorrow.

@artembilan artembilan added this to the 5.5 GA milestone May 4, 2021
@artembilan artembilan added backport 5.3.x in: core type: bug in: dsl and removed status: waiting-for-triage The issue need to be evaluated and its future decided in: core labels May 4, 2021
@agebhar1
Copy link
Contributor Author

agebhar1 commented May 5, 2021

This coincides with what I had seen in #3558 (comment)

Thank you for taking care of the issue!

@artembilan artembilan self-assigned this May 5, 2021
artembilan added a commit to artembilan/spring-integration that referenced this issue May 5, 2021
Fixes spring-projects#3558

Kotlin lambdas mostly used to configure endpoints in DSL manner
are not really Java lambdas, but rather anonymous classes implementing
respective Java interfaces.

While in most cases such classes carry generic info for their method impls
properly in Java, it is somehow doesn't work well for `GenericHandler`
implemented by Kotlin lambdas

* Wrap provided `GenericHandler` in the `BaseIntegrationFlowDefinition.handle()`
into a Java lambda and call `handle()` recursively to carry an expected type to
the `LambdaMessageProcessor`
* Fix `LambdaMessageProcessor` to handle `ClassUtils.isKotlinUnit()` result of
an invocation as a `null` reply

**Cherry-pick to `5.4.x` & `5.3.x`**
@artembilan
Copy link
Member

See related PR: #3561

garyrussell pushed a commit that referenced this issue May 5, 2021
Fixes #3558

Kotlin lambdas mostly used to configure endpoints in DSL manner
are not really Java lambdas, but rather anonymous classes implementing
respective Java interfaces.

While in most cases such classes carry generic info for their method impls
properly in Java, it is somehow doesn't work well for `GenericHandler`
implemented by Kotlin lambdas

* Wrap provided `GenericHandler` in the `BaseIntegrationFlowDefinition.handle()`
into a Java lambda and call `handle()` recursively to carry an expected type to
the `LambdaMessageProcessor`
* Fix `LambdaMessageProcessor` to handle `ClassUtils.isKotlinUnit()` result of
an invocation as a `null` reply

**Cherry-pick to `5.4.x` & `5.3.x`**
garyrussell pushed a commit that referenced this issue May 5, 2021
Fixes #3558

Kotlin lambdas mostly used to configure endpoints in DSL manner
are not really Java lambdas, but rather anonymous classes implementing
respective Java interfaces.

While in most cases such classes carry generic info for their method impls
properly in Java, it is somehow doesn't work well for `GenericHandler`
implemented by Kotlin lambdas

* Wrap provided `GenericHandler` in the `BaseIntegrationFlowDefinition.handle()`
into a Java lambda and call `handle()` recursively to carry an expected type to
the `LambdaMessageProcessor`
* Fix `LambdaMessageProcessor` to handle `ClassUtils.isKotlinUnit()` result of
an invocation as a `null` reply

**Cherry-pick to `5.4.x` & `5.3.x`**
garyrussell pushed a commit that referenced this issue May 5, 2021
Fixes #3558

Kotlin lambdas mostly used to configure endpoints in DSL manner
are not really Java lambdas, but rather anonymous classes implementing
respective Java interfaces.

While in most cases such classes carry generic info for their method impls
properly in Java, it is somehow doesn't work well for `GenericHandler`
implemented by Kotlin lambdas

* Wrap provided `GenericHandler` in the `BaseIntegrationFlowDefinition.handle()`
into a Java lambda and call `handle()` recursively to carry an expected type to
the `LambdaMessageProcessor`
* Fix `LambdaMessageProcessor` to handle `ClassUtils.isKotlinUnit()` result of
an invocation as a `null` reply

**Cherry-pick to `5.4.x` & `5.3.x`**
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants