Affects: 5.3.2


I am trying to programatically register ApplicationListener via lambda and ApplicationListener.forPayload like this:

class EventBusListenersRegistration(
  private val applicationContext: ConfigurableApplicationContext,
  private val eventListenersProvider: ObjectProvider<EventListener<TestEvent>>
): ApplicationRunner {
  override fun run(args: ApplicationArguments?) {
    eventListenersProvider.forEach{ eventListener ->
      applicationContext.addApplicationListener(ApplicationListener.forPayload<TestEvent> { eventListener.on(it) }) }
  }
}

and I am getting ClassCastException since this does not match. ReadinessState is payload of AvailabilityChangeEvent. I guess there is something wrong how payload is handled Spring ClassCastException handling at SimpleApplicationEventMulticaster of payload items

Comment From: bukajsytlos

I "hacked" it like this

private boolean matchesClassCastMessage(String classCastMessage, ApplicationEvent event) {
  Class<?> eventClass = event.getClass();
  if (event instanceof PayloadApplicationEvent) {
    eventClass = ((PayloadApplicationEvent) event).getPayload().getClass();
  }
  // On Java 8, the message starts with the class name: "java.lang.String cannot be cast..."
  if (classCastMessage.startsWith(eventClass.getName())) {
    return true;
  }
...

Is this correct solution?

Comment From: snicoll

@bukajsytlos sorry but I don't really understand what's happening here. Can you please move that code in text in a small project (for instance created from start.spring.io) and share it here? (something that we could run ourselves and that would reproduce the exception. Thank you.

Comment From: bukajsytlos

yes sorry, just run test and you get

java.lang.ClassCastException: class org.springframework.boot.availability.ReadinessState cannot be cast to class com.example.springframework.issue26349.TestEvent (org.springframework.boot.availability.ReadinessState and com.example.springframework.issue26349.TestEvent are in unnamed module of loader 'app')
    at org.springframework.context.ApplicationListener.lambda$forPayload$0(ApplicationListener.java:59) ~[spring-context-5.3.2.jar:5.3.2]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172) ~[spring-context-5.3.2.jar:5.3.2]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165) ~[spring-context-5.3.2.jar:5.3.2]

issue-26349.zip

Comment From: bukajsytlos

Looking at past issues, this is the domain of @jhoeller

Comment From: snicoll

Thanks @bukajsytlos there's no need to ping individual team members.

Comment From: snicoll

Thank you for sharing a sample.

ApplicationListener.<TestEvent>forPayload( is a classic case of type erasure. While you can "force" the type so that the consumer matches your expectation, this is going to register an ApplicationListener<Object> down the line. This listener is therefore listening to everything since its payload type is not constrained, hence the exception you're experiencing.

I've pinged Juergen earlier this afternoon to understand why this method has been introduced in 5.3 and how we could make this more apparent to users.