I had an issue which was missed due to exceptions not being thrown within spring event listeners.
Essentially the series of events went:
- MyEvent published
- listener1 is invoked
- listener2 is invoked, but encounters an exception
- No indication that an exception occurred (eg in console/logs)
- listener3 is NOT invoked
However, when I added configuration for the SimpleApplicationEventMulticaster, defining an errorHandler which just logs the exception, the following happened:
- MyEvent published
- listener1 is invoked
- listener2 is invoked, but encounters an exception
- listener3 is invoked
- exception is logged via my errorHandler
My Question
If the application will not proceed to the next listener following an exception in the default configuration, why does it do it when I configure it to print the exception?
I have a feeling that the exception being ‘invisible’ (no indication that the exception occurred) may be down to my use of Executors? However, I don’t understand the inconsistency in the exceptions being ‘blocking’ or not.
My Test Setup:
@Configuration
public class AppConfig {
// If the following configuration is present (just logging the NPE), all listeners are invoked.
// However, if it is removed, the NPE is not recorded, and only listeners 1 and 2 are invoked.
@Bean
public SimpleApplicationEventMulticaster applicationEventMulticaster() {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
multicaster.setErrorHandler(throwable -> {
System.err.println("Exception in event listener: " + throwable.getMessage());
});
return multicaster;
}
}
@EnableScheduling
@Component
public class EventsTesting {
private final ApplicationEventPublisher publisher;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
@Autowired
private EventsTesting(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
// PUBLISHING MyEvent EVERY 5 SECONDS
@Scheduled(fixedRate = 5000)
private void publishEvent() {
System.out.println("PUBLISH EVENT");
executor.submit(() -> publisher.publishEvent(new MyEvent(this)));
}
// EVENT LISTENERS HANDLE THE EVENT SEQUENTIALLY
@Order(1)
@EventListener
private void handleMyEvent1(MyEvent event) {
System.out.println("LISTENER 01");
}
@Order(2)
@EventListener
private void handleMyEvent2(MyEvent event) {
System.out.println("LISTENER 02");
String npe = event.getNull().toLowerCase();
}
@Order(3)
@EventListener
private void handleMyEvent3(MyEvent event) {
System.out.println("LISTENER 03");
}
public static class MyEvent extends ApplicationEvent {
public MyEvent(Object source) {
super(source);
}
public String getNull() { return null;}
}
}
With the SimpleApplicationEventMulticaster configuration present, I see the following logs:
PUBLISH EVENT
LISTENER 01
LISTENER 02
LISTENER 03
Exception in event listener: Cannot invoke "String.toLowerCase()" because the return value of "com.example.eventhandlingbug.EventsTesting$MyEvent.getNull()" is null
PUBLISH EVENT
LISTENER 01
LISTENER 02
LISTENER 03
Exception in event listener: Cannot invoke "String.toLowerCase()" because the return value of "com.example.eventhandlingbug.EventsTesting$MyEvent.getNull()" is null
However, without the configuration I see this:
PUBLISH EVENT
LISTENER 01
LISTENER 02
PUBLISH EVENT
LISTENER 01
LISTENER 02