Search code examples
error-handlingspring-integration

Spring Integration - Rollback on JMS MessageDrivenChannelAdapter without log pollution


I'm using Spring Integration (with Java DSL) to consume messages from JMS (MQ and Solace).

In some cases, my flow throws an exception and I'd like the message to be rolled back to transport and retried.

My current configuration looks like this

@Bean
IntegrationFlow myFlow() {
   return IntegrationFlows.from(Jms.messageDrivenChannelAdapter(conFactory)
                                   .configureListenerContainer(c -> c.sessionTransacted(true))
                                   .destination(myQueue))
                          .handle(myHandler)
                          .get();
   )
}

If myHandler throws exception, the message is rolled back and redelivered/reprocessed as expected, however I also see the following in the logs followed by the exception stacktrace.

... WARN ... DefaultMessageListenerContainer : Execution of JMS message listener failed, and no ErrorHandler has been set.

Since in some cases this behaviour (i.e. certain type of exception detected that should result in message re-processing) is desired I'd like to suppress the exception and WARN entry from polluting the logs.

I tried adding an errorChannel, i.e.

@Bean
IntegrationFlow myFlow() {
   return IntegrationFlows.from(Jms.messageDrivenChannelAdapter(conFactory)
                                   .configureListenerContainer(c -> c.sessionTransacted(true))
                                   .destination(myQueue)
                                   .errorChannel(myErrorChannel))
                          .handle(myHandler)
                          .get();
   )
}

@Bean
IntegrationFlow myErrorHandlingFlow() {
   return IntegrationFlows.from(myErrorChannel)
                          .handle(myErrorHandler)
                          .get();
   )
}

This suppressed the WARN log entry with the stacktrace, however, the messages are not being rolled back and are not redelivered/reprocessed anymore.

Is there some way how to use a custom error handler (so that I can modify what gets logged and how in case of exception), but at the same time keep the transaction rollback and message redelivery? (Note: I trie re-throwing the exception from myErrorHandler, however this just lead back to original behaviour with WARN + stacktrace in the logs.)


Solution

  • As you see according to the WARN log message, it happens in the DefaultMessageListenerContainer. The Jms.messageDrivenChannelAdapter is based on that, indeed, but it stays a bit higher in the stack with its Messaging abstraction. Therefore the mentioned errorChannel is the part if this Spring Integration layer and has no effect on the DefaultMessageListenerContainer. You just suppress an exception in your error flow and therefore there is no that warning logged, but at the same time the transaction is committed. Just because you have not thrown exception to the container back to determine its next movement.

    See .configureListenerContainer(container -> container.errorHandler(ErrorHandler)). Just exactly how that warning is suggesting. And looks like here you already can suppress an exception: the transaction is still going to be rolled back:

    protected void executeListener(Session session, Message message) {
        try {
            doExecuteListener(session, message);
        }
        catch (Throwable ex) {
            handleListenerException(ex);
        }
    }
    
    ...
        try {
            Observation observation = createObservation(message);
            observation.observeChecked(() -> invokeListener(session, message));
        }
        catch (JMSException | RuntimeException | Error ex) {
            rollbackOnExceptionIfNecessary(session, ex);
            throw ex;
        }
        commitIfNecessary(session, message);