iLearn
iLearn

Reputation: 1179

Spring JMS Message doesn't retry or goes to Backout Queue on Error

I'm using Spring JMS and MQ to Send and Receive Messages. While reading messages, I want to make sure that on any Error, the messages will be re-delivered at least 3 times and then later send them to the Backout Queue. The Main Queue has Backout Threshold of 3.

I am using the Transacted set to True and sessionAcknowledgeMode to CLIENT_ACKNOWLEDGE (value is 2). And in the Message Listener, i'm also doing message.acknowledge(); However, it's not working. Am I missing anything?

<jee:jndi-lookup id="QConnectionFactory" jndi-name="jndi/QCF"/>
<jee:jndi-lookup id="MainQ" jndi-name="jndi/MainQ"/>
<jee:jndi-lookup id="MainQBO" jndi-name="jndi/MainQBO"/>

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
    <property name="connectionFactory">
        <ref local="QConnectionFactory"/>
    </property>
    <property name="sessionAcknowledgeMode" value="2" />
    <property name="sessionTransacted" value="true" />
</bean>

<bean id="msgHandler" class="myservice.MyMessageHandler">
    <property name="jmsTemplate" ref="jmsTemplate"/>
    <property name="MainQ" ref="MainQ"/>
    <property name="MainQBO" ref="MainQBO"/>
</bean>


<bean id="messageListener" class="myservice.MyMessageListener" />
<bean id="jmsContainer"
    class="org.springframework.jms.listener.DefaultMessageListenerContainer">
    <property name="connectionFactory" ref="QConnectionFactory"/>
    <property name="destination" ref="MainQ"/>
    <property name="messageListener" ref="messageListener" />
    <property name="sessionTransacted" value="true"/>
    <property name="sessionAcknowledgeMode" value="2"/>
</bean>

Listener Class ...
public void onMessage(Message message) {
    try{
         ... some code ...
         message.acknowledge();
    }catch(Exception E){
        logger.erro(e);
    }
}

Upvotes: 0

Views: 3340

Answers (3)

leonidos79
leonidos79

Reputation: 149

This may be a late answer to the OP question, but in case you have management access to the IBM MQ server (the queue manager) the application connects to, it is possible to essentially delegate all the message retry to MQ own algorithms, called "message backout policy" for poison messages i.e. messages that cannot be properly handled by the consuming application for any reason.

Refer to https://www.ibm.com/docs/en/ibm-mq/9.1?topic=applications-handling-poison-messages-in-mq-classes-jms for details.

Note that MQ will happily handle the backout counting (i.e. how many times the same message has been re-delivered), backout threshold (how many failures in a row equal to a message being "poisonous"), and the backout queue (a soft dead-letter queue where poison messages will be routed to once they exceed the backout threshold) configuration for you, BUT this mechanism provides no convenient means for a) delaying the message re-delivery, and b) selective message re-delivery (which messages to attempt again, and which ones should be rejected right away). At least in IBM MQ Series 9 as the time of writing this. If this is something required by your business flow, the application has to implement it.

P.S. And do not forget about setting the sessionTransacted property of JMS message listener container to true so it will properly backout messages on exceptions raised by application!

Upvotes: 0

iLearn
iLearn

Reputation: 1179

I was able to fix the problem using the following approach. I hope it will help others.


Configuration

<bean id="messageListener" class="messageListenerClass" />
<bean id="fixedBackOff" class="org.springframework.util.backoff.FixedBackOff">
    <constructor-arg index="0" value="30000" />
    <constructor-arg index="1" value="3" />
</bean>
<bean id="jmsContainer"
    class="org.springframework.jms.listener.DefaultMessageListenerContainer">
    <property name="connectionFactory"  ref="connectionFactoryBean" />
    <property name="destination"        ref="destinationQueue" />
    <property name="messageListener"    ref="messageListener" />
    <property name="sessionTransacted"  value="true" />
    <property name="backOff"            ref="fixedBackOff" />
</bean>

Listener Class

@Override
public void onMessage(Message message) {
    if (message instanceof TextMessage) {
        try {
            String msg = ((TextMessage) message).getText();
            ........
            ........
        } catch (JMSException e) {
            logger.error("JMSException occured while reading the JMSMessage : {}", e);
            throw new RuntimeException();
        } catch (SomeException e) {
            logger.error("SomeException ... : {}", e);
            throw new RuntimeException();
        } catch (SomeException e) {
            logger.error("SomeException ... : {}", e);
            throw new RuntimeException();
        }
    } else {
        logger.error("Message must be of type TextMessage");
        throw new IllegalArgumentException("Message must be of type TextMessage");
    }
}

Upvotes: 0

Gary Russell
Gary Russell

Reputation: 174554

"Not working" is never enough information.

That said

}catch(Exception E){
    logger.erro(e);
}

You are catching and eating the exception; the listener needs to throw an exception to requeue the message.

Upvotes: 1

Related Questions