Search code examples
javajsonjacksonspring-jms

Spring JMS MappingJackson2MessageConverter - No way to handle errors


I am using Spring JMS, and I am using the MappingJackson2MessageConverter to pass along messages between various components.

My core problem is that when the MappingJackson2MessageConverter experiences an internal deserialization error, when deserializing a String into some Object, these exceptions are not raised in any way that I can respond to them. My Error Handler logic never gets called, and no exceptions are thrown that I have access to. These deserialization errors appear to be completely swallowed and, while they are logged in the console, I am offered no chance to perform custom logic in response to them.

I define my MappingJackson2MessageConverter bean as follows:

  @Bean
  public MessageConverter jacksonJmsMessageConverter() {
    MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
    converter.setTargetType(MessageType.TEXT);
    converter.setTypeIdPropertyName("_type");
    return converter;
  }

I define a JMS Listener factory as follows:

  @Bean
  public JmsListenerContainerFactory<?> jmsListenerQueueFactory(ConnectionFactory connectionFactory,
      DefaultJmsListenerContainerFactoryConfigurer configurer, JmsErrorHandler jmsErrorHandler) {
    DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
    factory.setErrorHandler(jmsErrorHandler); // <-- I expect this to be used
    configurer.configure(factory, connectionFactory);

    return factory;
  }

Of importance in the above line is the setErrorHandler call. I have defined a simple ErrorHandler object that is attempting to catch errors that my @JmsListener methods may throw. This simple error handler looks like this:

  public class JmsErrorHandler implements ErrorHandler {

  @Override
  public void handleError(Throwable t) {
    System.out.println("WE HAVE THE ERROR!");
  }
}

My listener is also simple, as follows:

  @JmsListener(id = MY_ID, destination = MY_DEST)
  public void processMessage(MyPojoMessage myPojoMessage) {
     // This code is obviously never reached, since the serialization error occurs. 

Overall, this is a very simple and basic setup. Now when I send an invalid message, that Jackson will fail to parse, I get the following errors in my console:

19-10-2020 09:05:33.933 [35m[DefaultMessageListenerContainer-1][0;39m [31mWARN [0;39m o.s.j.l.DefaultMessageListenerContainer.invokeErrorHandler - Execution of JMS message listener failed, and no ErrorHandler has been set.

org.springframework.jms.listener.adapter.ListenerExecutionFailedException: Listener method 'public void com.example.processMessage(com.example.MyPojoMessage)' threw exception; nested exception is org.springframework.jms.support.converter.MessageConversionException: Failed to convert JSON message content; nested exception is com.fasterxml.jackson.databind.JsonMappingException

This exception is expected - since I provide an invalid payload that Jackson cannot serialize. However, there is seemingly no way for me to configure the code to actually catch this exception in a way I can respond to it. I am setting the Error Handler in the above code. Yet the logs complain that no ErrorHandler has been set which seems wrong.

I see no other ways to get a reference to this Jackson inner exception. Is there any way?


Solution

  • Looks like ConditionalOnMissingBean autoconfiguration is on bean name

    So you should have something like this.

    @Bean
      public JmsListenerContainerFactory<?> jmsListenerContainerFactory(ConnectionFactory connectionFactory,
          DefaultJmsListenerContainerFactoryConfigurer configurer, JmsErrorHandler jmsErrorHandler) {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setErrorHandler(jmsErrorHandler); // <-- I expect this to be used
        configurer.configure(factory, connectionFactory);
        return factory;
      }