Connect to remote jms queue with Spring Boot - spring-boot

I'm trying to connect a remote JBOSS, JBOSS EAP 7, JMS queue with spring boot.
Below my code, I have only a configuration class and consumer class.
I see through logs that I'm connecting to localhost, but... WHY???
#Configuration
#EnableJms
public class ActiveMqConnectionFactoryConfig {
String queueName= "JMSTestQueueName";
private static final String INITIAL_CONTEXT_FACTORY = "org.jboss.naming.remote.client.InitialContextFactory";
private static final String CONNECTION_FACTORY = "jms/RemoteConnectionFactory";
public ConnectionFactory connectionFactory() {
try {
System.out.println("Retrieving JMS queue with JNDI name: " + CONNECTION_FACTORY);
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName(CONNECTION_FACTORY);
jndiObjectFactoryBean.setJndiEnvironment(getEnvProperties());
jndiObjectFactoryBean.afterPropertiesSet();
return (QueueConnectionFactory) jndiObjectFactoryBean.getObject();
} catch (NamingException e) {
System.out.println("Error while retrieving JMS queue with JNDI name: [" + CONNECTION_FACTORY + "]");
} catch (Exception ex) {
System.out.println("Error error");
ex.getStackTrace();
}
return null;
}
#Bean
Properties getEnvProperties() throws NamingException {
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
env.put(Context.PROVIDER_URL, "http-remoting://<REMOTE_ADDRESS>:8080?broker.persistent=false&broker.useJmx=false");
env.put(Context.SECURITY_PRINCIPAL, <USER>);
env.put(Context.SECURITY_CREDENTIALS, <PASSWORD>);
namingContext = new InitialContext(env);
return env;
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory connectionFactory) throws NamingException {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setConcurrency("3-10");
JndiDestinationResolver jndiDestinationResolver = new JndiDestinationResolver();
jndiDestinationResolver.setJndiEnvironment(getEnvProperties());
factory.setDestinationResolver(jndiDestinationResolver);
return factory;
}
}
Listener Class:
#Configuration
public class jmsListener {
#JmsListener(destination = "queue/JMSTestQueueName", containerFactory = "jmsListenerContainerFactory")
public void receive(Message message) {
System.out.println("Received Message: " + message);
}
}
The log:
2020-05-27 00:21:58.571 INFO 10368 --- [ main] o.apache.activemq.broker.BrokerService : Apache ActiveMQ 5.15.7 (localhost, ID:ITPC020248-60530-1590531718443-0:1) is starting
2020-05-27 00:21:58.576 INFO 10368 --- [ main] o.apache.activemq.broker.BrokerService : Apache ActiveMQ 5.15.7 (localhost, ID:ITPC020248-60530-1590531718443-0:1) started
2020-05-27 00:21:58.577 INFO 10368 --- [ main] o.apache.activemq.broker.BrokerService : For help or more information please see: http://activemq.apache.org
2020-05-27 00:21:58.617 INFO 10368 --- [ main] o.a.activemq.broker.TransportConnector : Connector vm://localhost started
2020-05-27 00:21:58.659 INFO 10368 --- [ main] jboss.ConsumerClass : Started ConsumerClass in 1.922 seconds (JVM running for 3.504)
Without ?broker.persistent=false&broker.useJmx=false on the JNDI URL I obtain this:
2020-05-27 19:38:34.680 INFO 19752 --- [dpoint" task-13] org.jboss.ejb.client.remoting : EJBCLIENT000016: Channel Channel ID 8f759d73 (outbound) of Remoting connection 336369ee to /172.16.68.80:8080 can no longer process messages
2020-05-27 19:38:37.479 INFO 19752 --- [ main] jboss.ConsumerClass : Started ConsumerClass in 7.194 seconds (JVM running for 9.26)
2020-05-27 19:38:42.772 ERROR 19752 --- [enerContainer-2] o.s.j.l.DefaultMessageListenerContainer : Could not refresh JMS Connection for destination 'queue/JMSTestQueueName' - retrying using FixedBackOff{interval=5000, currentAttempts=0, maxAttempts=unlimited}. Cause: AMQ119031: Unable to validate user
2020-05-27 19:38:48.068 ERROR 19752 --- [enerContainer-2] o.s.j.l.DefaultMessageListenerContainer : Could not refresh JMS Connection for destination 'queue/JMSTestQueueName' - retrying using FixedBackOff{interval=5000, currentAttempts=1, maxAttempts=unlimited}. Cause: AMQ119031: Unable to validate user
2020-05-27 19:38:53.401 ERROR 19752 --- [enerContainer-2] o.s.j.l.DefaultMessageListenerContainer : Could not refresh JMS Connection for destination 'queue/JMSTestQueueName' - retrying using FixedBackOff{interval=5000, currentAttempts=2, maxAttempts=unlimited}. Cause: AMQ119031: Unable to validate user
2020-05-27 19:38:58.699 ERROR 19752 --- [enerContainer-2] o.s.j.l.DefaultMessageListenerContainer : Could not refresh JMS Connection for destination 'queue/JMSTestQueueName' - retrying using FixedBackOff{interval=5000, currentAttempts=3, maxAttempts=unlimited}. Cause: AMQ119031: Unable to validate user
2020-05-27 19:39:04.006 ERROR 19752 --- [enerContainer-2] o.s.j.l.DefaultMessageListenerContainer : Could not refresh JMS Connection for destination 'queue/JMSTestQueueName' - retrying using FixedBackOff{interval=5000, currentAttempts=4, maxAttempts=unlimited}. Cause: AMQ119031: Unable to validate user
2020-05-27 19:39:09.320 ERROR 19752 --- [enerContainer-2] o.s.j.l.DefaultMessageListenerContainer : Could not refresh JMS Connection for destination 'queue/JMSTestQueueName' - retrying using FixedBackOff{interval=5000, currentAttempts=5, maxAttempts=unlimited}. Cause: AMQ119031: Unable to validate user

Your connectionFactory() needs to be a #Bean in order to inject it as an argument into your container factory factory method.
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory connectionFactory) throws NamingException {
Since it's not a #Bean you are getting boot's default connection factory (presumably you have ActiveMQ on the class path).

Related

How to implement Retry logic in RabbitMQ?

In my project I'm setting SimpleRetryPolicy to add custom exception and RetryOperationsInterceptor which is consuming this policy.
#Bean
public SimpleRetryPolicy rejectionRetryPolicy() {
Map<Class<? extends Throwable>, Boolean> exceptionsMap = new HashMap<Class<? extends Throwable>, Boolean>();
exceptionsMap.put(DoNotRetryException.class, false);//not retriable
exceptionsMap.put(RetryException.class, true); //retriable
return new SimpleRetryPolicy(3, exceptionsMap, true);
}
#Bean
RetryOperationsInterceptor interceptor() {
return RetryInterceptorBuilder.stateless()
.retryPolicy(rejectionRetryPolicy())
.backOffOptions(2000L, 2, 3000L)
.recoverer(
new RepublishMessageRecoverer(rabbitTemplate(), "dlExchange", "dlRoutingKey"))
.build();
}
But with these configurations retry is not working for both RetryException and DoNotRetryException, where I want RetryException to be retried finite number of time and DoNotRetryException to send to DLQ
Please help with the issue, I'm attaching repo link if in case of need.
https://github.com/aviralnimbekar/RabbitMQ/tree/main/src
Your GlobalErrorHandler does its logic before the retry happens and you override there an exception with the AmqpRejectAndDontRequeueException. And looks like you do there a publish to DLX. Consider to move your GlobalErrorHandler logic to a more general ErrorHandler for the factory.setErrorHandler(); instead.
See more info in docs: https://docs.spring.io/spring-amqp/reference/html/#exception-handling
UPDATE
After removing errorHandler = "globalErrorHandler" from your #RabbitListener, I got this in logs:
2022-08-03 16:02:08.093 INFO 16896 --- [nio-8080-exec-4] c.t.r.producer.RabbitMQProducer : Message sent -> retry
2022-08-03 16:02:08.095 INFO 16896 --- [ntContainer#0-1] c.t.r.consumer.RabbitMQConsumer : Retrying message...
2022-08-03 16:02:10.096 INFO 16896 --- [ntContainer#0-1] c.t.r.consumer.RabbitMQConsumer : Retrying message...
2022-08-03 16:02:13.099 INFO 16896 --- [ntContainer#0-1] c.t.r.consumer.RabbitMQConsumer : Retrying message...
2022-08-03 16:02:13.100 WARN 16896 --- [ntContainer#0-1] o.s.a.r.retry.RepublishMessageRecoverer : Republishing failed message to exchange 'dlExchange' with routing key dlRoutingKey
2022-08-03 16:02:17.736 INFO 16896 --- [nio-8080-exec-5] c.t.r.producer.RabbitMQProducer : Message sent -> 1231231
2022-08-03 16:02:17.738 INFO 16896 --- [ntContainer#0-1] c.t.r.consumer.RabbitMQConsumer : sending into dlq...
2022-08-03 16:02:17.739 WARN 16896 --- [ntContainer#0-1] o.s.a.r.retry.RepublishMessageRecoverer : Republishing failed message to exchange 'dlExchange' with routing key dlRoutingKey
Which definitely reflects your original requirements.

How to close Rabbitmq Connection before Spring context

Spring - Close RabbitMQ Consumer connection without closing spring application
We have a requirement we want to close the rabbitmq connection, wait for few minutes before cleaning up other resources and then closing the spring application. We have a buffer of 6 minutes to clean up the other resources. However, during this time, the rabbitmq connection is auto recreated.
We are creating the connections as follows...
#Configuration
public class RabbitMQSubscribersHolder extends BaseRabbitMQConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(RabbitMQSubscribersHolder.class);
private ConcurrentMap<String, SimpleMessageListenerContainer> containers = new ConcurrentHashMap();
public RabbitMQSubscribersHolder() {
}
public void addSubscriber(String mqAlias, MessageListener receiver) {
ConnectionFactory connectionFactory = this.connectionFactory(mqAlias);
RabbitAdmin admin = null;
try {
admin = new RabbitAdmin(connectionFactory);
AbstractExchange exchange = this.exchange(mqAlias);
admin.declareExchange(exchange);
Queue queue = null;
if (!StringUtils.isEmpty(this.getMqQueueNameForQueueAlias(mqAlias))) {
queue = this.queue(mqAlias);
admin.declareQueue(queue);
} else {
queue = admin.declareQueue();
}
admin.declareBinding(this.binding(mqAlias, queue, exchange));
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(new String[]{queue.getName()});
container.setMessageListener(this.listenerAdapter(receiver));
container.setRabbitAdmin(admin);
container.start();
this.containers.put(mqAlias, container);
} catch (Exception var8) {
LOGGER.error(var8.getMessage(), var8);
}
}
MessageListenerAdapter listenerAdapter(MessageListener receiver) {
return new MessageListenerAdapter(receiver, "onMessage");
}
public SimpleMessageListenerContainer getContainer(String mqAlias) {
return (SimpleMessageListenerContainer)this.containers.get(mqAlias);
}
This is our destroy method
public void destroy() {
Iterator var1 = this.aliasToConnectionFactory.entrySet().iterator();
while(var1.hasNext()) {
Map.Entry<String, CachingConnectionFactory> entry = (Map.Entry)var1.next();
try {
((CachingConnectionFactory)entry.getValue()).destroy();
LOGGER.info("RabbitMQ caching connection factory closed for alias: {}", entry.getKey());
} catch (Exception var4) {
LOGGER.error("RabbitMQ caching connection destroy operation failed for alias: {} due to {}", entry.getKey(), StringUtils.getStackTrace(var4));
}
}
}
It seems the connection is re-created automatically as soon as it is destroyed (verified from Rabbit UI). Here are the logs:
2022-07-11 19:42:55.334 INFO 35761 --- [ Thread-11] c.g.f.f.r.GracefulShutdownRabbitMQ : RabbitMQ caching connection factory closed for alias: preMatchFreeze
2022-07-11 19:42:55.395 INFO 35761 --- [cTaskExecutor-1] o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer: tags=[{amq.ctag-tSAgXuPdMnLd9q79ryBWGg=fury_pre_matchfreeze_queue}], channel=Cached Rabbit Channel: AMQChannel(amqp://gwportal#10.24.128.76:5672/,1), conn: Proxy#205b73d8 Shared Rabbit Connection: null, acknowledgeMode=AUTO local queue size=0
2022-07-11 19:42:55.405 INFO 35761 --- [ Thread-11] c.g.f.f.r.GracefulShutdownRabbitMQ : RabbitMQ caching connection factory closed for alias: parentMatchFreezeEds
2022-07-11 19:42:55.447 INFO 35761 --- [ Thread-11] c.g.f.f.r.GracefulShutdownRabbitMQ : RabbitMQ caching connection factory closed for alias: fantasy_matchFreeze
2022-07-11 19:42:55.489 INFO 35761 --- [ Thread-11] c.g.f.f.r.GracefulShutdownRabbitMQ : RabbitMQ caching connection factory closed for alias: parentMatchFreezeCore
2022-07-11 19:42:55.489 INFO 35761 --- [ Thread-11] c.g.f.fury.GracefulShutdownHandler : Waiting before starting graceful shutdown
2022-07-11 19:42:55.794 INFO 35761 --- [cTaskExecutor-1] o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer: tags=[{amq.ctag-sK8_FY4PtHW52HXkQV3S_w=fury_match_freeze_queue}], channel=Cached Rabbit Channel: AMQChannel(amqp://gwportal#10.24.128.76:5672/,1), conn: Proxy#4bc9389 Shared Rabbit Connection: null, acknowledgeMode=AUTO local queue size=0
2022-07-11 19:42:56.046 INFO 35761 --- [cTaskExecutor-2] o.s.a.r.c.CachingConnectionFactory : Created new connection: SimpleConnection#3828e912 [delegate=amqp://gwportal#10.24.128.76:5672/, localPort= 58170]
2022-07-11 19:42:56.098 INFO 35761 --- [cTaskExecutor-1] o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer: tags=[{amq.ctag-KyvnWaqZco9NRTessjupJQ=fantasy_parent_match_freeze_queue}], channel=Cached Rabbit Channel: AMQChannel(amqp://gwportal#10.24.128.76:5672/,1), conn: Proxy#17d45cfb Shared Rabbit Connection: null, acknowledgeMode=AUTO local queue size=0
2022-07-11 19:43:13.127 INFO 35761 --- [cTaskExecutor-2] o.s.a.r.c.CachingConnectionFactory : Created new connection: SimpleConnection#4c3cefad [delegate=amqp://gwportal#10.24.141.77:5672/, localPort= 58182]
2022-07-11 19:43:16.849 INFO 35761 --- [cTaskExecutor-2] o.s.a.r.c.CachingConnectionFactory : Created new connection: SimpleConnection#801eaf8 [delegate=amqp://gwportal#10.24.141.77:5672/, localPort= 58185]
You need to stop the message listener container(s) to prevent them from trying to reconnect.

Setup of JMS message listener invoker failed for destination 'TESTQUEUE' - trying to recover. Cause: null

My application run well in my local machine and i am able to connect to IBM MQ and poll message. I am facing below issue in polling after i deploy my application on open shift.
Error:
2022-05-03 02:02:24.418 DEBUG 7 --- [ main] o.s.j.c.CachingConnectionFactory : Established shared JMS Connection: com.ibm.mq.jms.MQConnection#397a10df
2022-05-03 02:02:24.422 DEBUG 7 --- [ main] o.s.j.l.DefaultMessageListenerContainer : Established shared JMS Connection
2022-05-03 02:02:24.423 DEBUG 7 --- [ main] o.s.j.l.DefaultMessageListenerContainer : Resumed paused task: org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker#55c46ec1
2022-05-03 02:02:24.623 INFO 7 --- [ main] c.c.com.poc.ibmmq.DemoApplication : Started DemoApplication in 84.7 seconds (JVM running for 103.063)
2022-05-03 02:02:30.922 WARN 7 --- [enerContainer-1] o.s.j.l.DefaultMessageListenerContainer : Setup of JMS message listener invoker failed for destination 'TESTQUEUE' - trying to recover. Cause: null
java.lang.NullPointerException: null
at com.ibm.mq.jms.MQSession.getTransacted(MQSession.java:876) ~[com.ibm.mq.allclient-9.1.5.0.jar!/:9.1.5.0 - p915-L200316]
at com.ibm.mq.jms.MQSession.<init>(MQSession.java:262) ~[com.ibm.mq.allclient-9.1.5.0.jar!/:9.1.5.0 - p915-L200316]
at com.ibm.mq.jms.MQConnection.createSession(MQConnection.java:336) ~[com.ibm.mq.allclient-9.1.5.0.jar!/:9.1.5.0 - p915-L200316]
at org.springframework.jms.connection.SingleConnectionFactory.createSession(SingleConnectionFactory.java:482) ~[spring-jms-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
at org.springframework.jms.connection.CachingConnectionFactory.getSession(CachingConnectionFactory.java:233) ~[spring-jms-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
at org.springframework.jms.connection.SingleConnectionFactory$SharedConnectionInvocationHandler.invoke(SingleConnectionFactory.java:649) ~[spring-jms-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
at com.sun.proxy.$Proxy148.createSession(Unknown Source) ~[na:na]
at org.springframework.jms.support.JmsAccessor.createSession(JmsAccessor.java:208) ~[spring-jms-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer.access$1500(DefaultMessageListenerContainer.java:126) ~[spring-jms-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.initResourcesIfNecessary(DefaultMessageListenerContainer.java:1213) ~[spring-jms-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1188) ~[spring-jms-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1179) ~[spring-jms-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1076) ~[spring-jms-5.2.8.RELEASE.jar!/:5.2.8.RELEASE]
at java.base/java.lang.Thread.run(Unknown Source) ~[na:na]
Code:
My configuration:
#Bean
public JmsListenerContainerFactory<?> myFactory(ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
setSSLProperties();
configurer.configure(factory,connectionFactory);
return factory;
}
Receiver:
#JmsListener(destination = "${response.queue}", containerFactory = "myFactory")
public void receiveMessage(JmsMessage test) throws JMSException {
logger.info("Response Sent {}", test.toString());
}

Spring boot rabbitMq setExceptionHandler

How can I convert the following code using the Spring framework?
ConnectionFactory factory = new ConnectionFactory();
factory.setExceptionHandler(new BrokerExceptionHandler(logger, instance));
public final class BrokerExceptionHandler extends StrictExceptionHandler {
#Override
public void handleReturnListenerException(Channel channel, Throwable exception) {
logger.log(Level.SEVERE, "ReturnListenerException detected: ReturnListener.handleReturn", exception);
this.publishAlert(exception, "ReturnListener.handleReturn");
logger.log(Level.SEVERE, "Close application", exception);
System.exit(-1);
}
....
}
Basically I need to specify a custom exception handler if a rabbitMQ exception occurs and then stop the application
How can I publish a rabbitMq message every time that there is an exception?
EDIT
I modified my configuration class in this way:
#Bean
SimpleMessageListenerContainer containerPredict(ConnectionFactory connectionFactory,
MessageListenerAdapter listenerPredictAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setDefaultRequeueRejected(false);
container.setErrorHandler(new BrokerExceptionHandler());
container.setQueueNames(getQueueName());
container.setMessageListener(listenerAdapter);
return container;
}
and this is my BrokerExceptionHandler class
public class BrokerExceptionHandler implements ErrorHandler {
private final Logger logger = Logger.getLogger(getClass().getSimpleName());
#Autowired
private Helper helper;
#Override
public void handleError(Throwable t) {
logger.log(Level.SEVERE, "Exception Detected. Publishing error alert");
String message = "Exception detected. Message: " + t.getMessage());
// Notify the error to the System sending a new RabbitMq message
System.out.println("---> Before convertAndSend");
rabbitTemplate.convertAndSend(exchange, routing, message);
System.out.println("---> After convertAndSend");
}
}
I can see the log Exception Detected. Publishing error alert and ---> Before convertAdnSend in the console, but the new alert is not published and the log ---> After convertAndSend doesn't appear in the console.
Here it is the log:
2018-10-17 09:32:02.849 ERROR 1506 --- [tainer****-1] BrokerExceptionHandler : Exception Detected. Publishing error alert
---> Before convertAndSend
2018-10-17 09:32:02.853 INFO 1506 --- [tainer****-1] o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer#4f5b08d: tags=[{amq.ctag-yUcUmg5BCo20ucG1wJZoWA=myechange}], channel=Cached Rabbit Channel: AMQChannel(amqp://admin#XXX.XXX.XXX.XXX:5672/testbed_simulators,1), conn: Proxy#3964d79 Shared Rabbit Connection: SimpleConnection#61f39bb [delegate=amqp://admin#XXX.XXX.XXX.XXX:5672/testbed_simulators, localPort= 51528], acknowledgeMode=AUTO local queue size=0
2018-10-17 09:32:02.905 INFO 1506 --- [tainer****-2] o.s.amqp.rabbit.core.RabbitAdmin : Auto-declaring a non-durable, auto-delete, or exclusive Queue (myexchange) durable:false, auto-delete:true, exclusive:true. It will be redeclared if the broker stops and is restarted while the connection factory is alive, but all messages will be lost.
2018-10-17 09:32:02.905 INFO 1506 --- [tainer****-2] o.s.amqp.rabbit.core.RabbitAdmin : Auto-declaring a non-durable, auto-delete, or exclusive Queue (myexchange) durable:false, auto-delete:true, exclusive:true. It will be redeclared if the broker stops and is restarted while the connection factory is alive, but all messages will be lost.
EDIT
Debugging I see that before to send the new message, the following code is called:
File: SimpleMessageListenerContainer.class line 1212
if (!isActive(this.consumer) || aborted) {
.....
}
else {
---> logger.info("Restarting " + this.consumer);
restart(this.consumer);
}
EDIT 2
Example code: http://github.com/fabry00/spring-boot-rabbitmq
It depends on how you are doing your configuration; if you are using Spring Boot's auto-configured connection factory...
#Bean
public InitializingBean connectionFactoryConfigurer(CachingConnectionFactory ccf) {
return () -> ccf.getRabbitConnectionFactory().setExceptionHandler(...);
}
If you are wiring up your own beans (e.g. via a RabbitConnectionFactoryBean) then set it directly.
EDIT
You are throwing a NullPointerException in your error handler...
2018-10-17 11:51:58.733 DEBUG 38975 --- [containerKpis-1] o.s.a.r.l.SimpleMessageListenerContainer : Consumer raised exception, processing can restart if the connection factory supports it
java.lang.NullPointerException: null
at com.test.BrokerExceptionHandler.handleError(BrokerExceptionHandler.java:27) ~[main/:na]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeErrorHandler(AbstractMessageListenerContainer.java:1243) ~[spring-rabbit-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.handleListenerException(AbstractMessageListenerContainer.java:1488) ~[spring-rabbit-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1318) ~[spring-rabbit-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:817) ~[spring-rabbit-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:801) ~[spring-rabbit-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$700(SimpleMessageListenerContainer.java:77) ~[spring-rabbit-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1042) ~[spring-rabbit-2.0.6.RELEASE.jar:2.0.6.RELEASE]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_131]
2018-10-17 11:51:58.734 INFO 38975 --- [containerKpis-1] o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer#1aabf50d: tags=[{amq.ctag-VxxHKiMsWI_w8DIooAsySA=myapp.mydomain.KPIS}], channel=Cached Rabbit Channel: AMQChannel(amqp://guest#127.0.0.1:5672/,1), conn: Proxy#b88a7d6 Shared Rabbit Connection: SimpleConnection#25dc64a [delegate=amqp://guest#127.0.0.1:5672/, localPort= 55662], acknowledgeMode=AUTO local queue size=0
To turn on DEBUG logging, add
logging.level.org.springframework.amqp=debug
to your application.properties.
this.helper is null because the error handler is not a Spring Bean - #Autowired only works if Spring manages the object; you are using new BrokerExceptionHandler().
EDIT2
I added these 2 beans
#Bean
public BrokerExceptionHandler errorHandler() {
return new BrokerExceptionHandler();
}
#Bean
public MessageConverter json() { // Boot auto-configures in template
return new Jackson2JsonMessageConverter();
}
and now...
---> Before publishing Alert event
--- ALERT
2018-10-17 12:14:45.304 INFO 43359 --- [containerKpis-1] Helper : publishAlert
2018-10-17 12:14:45.321 DEBUG 43359 --- [containerKpis-1] o.s.a.r.c.CachingConnectionFactory : Creating cached Rabbit Channel from AMQChannel(amqp://guest#127.0.0.1:5672/,3)
2018-10-17 12:14:45.321 DEBUG 43359 --- [containerKpis-1] o.s.amqp.rabbit.core.RabbitTemplate : Executing callback RabbitTemplate$$Lambda$638/975724213 on RabbitMQ Channel: Cached Rabbit Channel: AMQChannel(amqp://guest#127.0.0.1:5672/,3), conn: Proxy#77f3f419 Shared Rabbit Connection: SimpleConnection#10c86af1 [delegate=amqp://guest#127.0.0.1:5672/, localPort= 56220]
2018-10-17 12:14:45.321 DEBUG 43359 --- [containerKpis-1] o.s.amqp.rabbit.core.RabbitTemplate : Publishing message (Body:'{"timestamp":1539792885303,"code":"ERROR","severity":"ERROR","message":"Exception detected. Message: Listener method 'kpisEvent' threw exception"}' MessageProperties [headers={sender=myapp, protocolVersion=1.0.0, senderType=MY_COMPONENT_1, __TypeId__=com.test.domain.Alert, timestamp=1539792885304}, contentType=application/json, contentEncoding=UTF-8, contentLength=146, deliveryMode=PERSISTENT, priority=0, deliveryTag=0])on exchange [myevent.ALERT], routingKey = [/]
--- ALERT 2
---> After publishing Alert event
2018-10-17 12:14:45.323 DEBUG 43359 --- [pool-1-thread-6] o.s.a.r.listener.BlockingQueueConsumer : Storing delivery for consumerTag: 'amq.ctag-eYbzZ09pCw3cjdtSprlZMQ' with deliveryTag: '1' in Consumer#4b790d86: tags=[{amq.ctag-eYbzZ09pCw3cjdtSprlZMQ=myapp.myevent.ALERT}], channel=Cached Rabbit Channel: AMQChannel(amqp://guest#127.0.0.1:5672/,2), conn: Proxy#77f3f419 Shared Rabbit Connection: SimpleConnection#10c86af1 [delegate=amqp://guest#127.0.0.1:5672/, localPort= 56220], acknowledgeMode=AUTO local queue size=0
2018-10-17 12:14:45.324 DEBUG 43359 --- [ontainerReset-1] o.s.a.r.listener.BlockingQueueConsumer : Received message: (Body:'{"timestamp":1539792885303,"code":"ERROR","severity":"ERROR","message":"Exception detected. Message: Listener method 'kpisEvent' threw exception"}' MessageProperties [headers={sender=myapp, protocolVersion=1.0.0, senderType=MY_COMPONENT_1, __TypeId__=com.test.domain.Alert, timestamp=1539792885304}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=false, receivedExchange=myevent.ALERT, receivedRoutingKey=/, deliveryTag=1, consumerTag=amq.ctag-eYbzZ09pCw3cjdtSprlZMQ, consumerQueue=myapp.myevent.ALERT])
2018-10-17 12:14:45.324 INFO 43359 --- [ontainerReset-1] Application : ---> kpisAlert RECEIVED
2018-10-17 12:14:45.325 ERROR 43359 --- [ontainerReset-1] Application : ---> Message: Exception detected. Message: Listener method 'kpisEvent' threw exception
2018-10-17 12:14:45.326 DEBUG 43359 --- [containerKpis-1] o.s.a.r.listener.BlockingQueueConsumer : Rejecting messages (requeue=false)
EDIT3
Or, if you prefer Gson...
#Bean
public MessageConverter json() {
Gson gson = new GsonBuilder().create();
return new MessageConverter() {
#Override
public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
return new Message(gson.toJson(object).getBytes(), messageProperties);
}
#Override
public Object fromMessage(Message message) throws MessageConversionException {
throw new UnsupportedOperationException();
}
};
}
EDIT4
I changed the current version of your app as follows:
#Bean
public MessageConverter jsonConverter() {
Gson gson = new GsonBuilder().create();
EventKpisCollected collected = new EventKpisCollected();
return new MessageConverter() {
#Override
public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
System.out.println("toMessage");
return new Message(gson.toJson(object).getBytes(), messageProperties);
}
#Override
public Object fromMessage(Message message) throws MessageConversionException {
System.out.println("fromMessage");
return collected.decode(new String(message.getBody()));
}
};
}
...
#Bean
SimpleMessageListenerContainer containerKpis(ConnectionFactory connectionFactory,
MessageListenerAdapter listenerKpisAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setDefaultRequeueRejected(false);
container.setErrorHandler(errorHandler());
container.setQueueNames(getQueueKpis());
container.setMessageListener(listenerKpisAdapter);
return container;
}
#Bean
SimpleMessageListenerContainer containerReset(ConnectionFactory connectionFactory,
MessageListenerAdapter listenerAlertAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setDefaultRequeueRejected(false);
container.setErrorHandler(errorHandler());
container.setQueueNames(getQueueAlert());
container.setMessageListener(listenerAlertAdapter);
return container;
}
#Bean
MessageListenerAdapter listenerKpisAdapter(Application receiver) {
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(receiver, "kpisEvent");
messageListenerAdapter.setMessageConverter(jsonConverter());
return messageListenerAdapter;
}
#Bean
MessageListenerAdapter listenerAlertAdapter(Application receiver) {
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(receiver, "alertEvent");
// messageListenerAdapter.setMessageConverter(jsonConverter()); converter only handles events.
return messageListenerAdapter;
}
and
fromMessage
2018-10-19 13:46:53.734 INFO 10725 --- [containerKpis-1] Application : ---> kpisEvent RECEIVED
2018-10-19 13:46:53.734 INFO 10725 --- [containerKpis-1] Application : ---> kpisEvent DECODED, windowId: 1522751098000-1522752198000
With the event decoding done by the framework (just for the event currently - you will need a second converter for the alers).

Spring JMS transaction rollback - message dequeued off ActiveMQ

I have a simple Spring Boot application(Spring Boot Version 1.5.3.RELEASE) for consuming JMS Messages off an ActiveMQ(version 5.14.5) Queue.
I want the messages to be consumed in a JMS transaction. If there is an exception during message consumption, I expect transaction to be rolled back and message not to be dequeued(taken off message queue). I can see transaction being rolled back in Spring logs, however the message is still dequeued from ActiveMQ queue(after six re delivery attempts).
Any pointers will be appreciated.
Here is the application code:
#SpringBootApplication
public class SpringJmsDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringJmsDemoApplication.class, args);
}
#Bean
public JmsListenerContainerFactory<?> myFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory defaultJmsListenerContainerFactory = new DefaultJmsListenerContainerFactory();
defaultJmsListenerContainerFactory.setTransactionManager(jmsTransactionManager(connectionFactory));
defaultJmsListenerContainerFactory.setSessionTransacted(true);
defaultJmsListenerContainerFactory.setSessionAcknowledgeMode(Session.SESSION_TRANSACTED);
configurer.configure(defaultJmsListenerContainerFactory, connectionFactory);
return defaultJmsListenerContainerFactory;
}
#Bean
public PlatformTransactionManager jmsTransactionManager(ConnectionFactory connectionFactory) {
return new JmsTransactionManager(connectionFactory);
}
}
#Component
public class Receiver {
#JmsListener(destination = "mailbox", containerFactory = "myFactory")
#Transactional
public void receiveMessage(String email) {
System.out.println("Received <" + email + ">");
throw new RuntimeException("nooo");
}
}
Here is the log:
2017-05-24 09:51:59.865 DEBUG 8972 --- [DefaultMessageListenerContainer-1] o.s.j.connection.JmsTransactionManager : Created JMS transaction on Session [ActiveMQSession {id=ID:D6C0B8467A518-58248-1495590693980-1:32:1,started=false} java.lang.Object#65d647cd] from Connection [ActiveMQConnection
2017-05-24 09:51:59.867 DEBUG 8972 --- [DefaultMessageListenerContainer-1] o.s.j.l.DefaultMessageListenerContainer : Received message of type [class org.apache.activemq.command.ActiveMQTextMessage] from consumer [ActiveMQMessageConsumer { value=ID:D6C0B8467A518-58248-1495590693980-1:32:1:1, started=true }] of transactional session [ActiveMQSession {id=ID:D6C0B8467A518-58248-1495590693980-1:32:1,started=true} java.lang.Object#65d647cd]
2017-05-24 09:51:59.867 DEBUG 8972 --- [DefaultMessageListenerContainer-1] o.s.j.l.DefaultMessageListenerContainer : Rolling back transaction because of listener exception thrown: org.springframework.jms.listener.adapter.ListenerExecutionFailedException: Listener method 'public void com.anz.markets.springjmsdemo.Receiver.receiveMessage(java.lang.String)' threw exception; nested exception is java.lang.RuntimeException: nooo
2017-05-24 09:51:59.867 WARN 8972 --- [DefaultMessageListenerContainer-1] o.s.j.l.DefaultMessageListenerContainer : Execution of JMS message listener failed, and no ErrorHandler has been set.
org.springframework.jms.listener.adapter.ListenerExecutionFailedException: Listener method 'public void com.anz.markets.springjmsdemo.Receiver.receiveMessage(java.lang.String)' threw exception; nested exception is java.lang.RuntimeException: nooo
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:112) ~[spring-jms-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:69) ~[spring-jms-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:721) ~[spring-jms-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:681) ~[spring-jms-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:651) ~[spring-jms-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:317) [spring-jms-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:235) [spring-jms-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1166) [spring-jms-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1158) [spring-jms-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1055) [spring-jms-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_72]
Caused by: java.lang.RuntimeException: nooo
at com.anz.markets.springjmsdemo.Receiver.receiveMessage(Receiver.java:12) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_72]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_72]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_72]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_72]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:180) ~[spring-messaging-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:112) ~[spring-messaging-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:104) ~[spring-jms-4.3.8.RELEASE.jar:4.3.8.RELEASE]
... 10 common frames omitted
2017-05-24 09:51:59.868 DEBUG 8972 --- [DefaultMessageListenerContainer-1] o.s.j.connection.JmsTransactionManager : Transactional code has requested rollback
2017-05-24 09:51:59.868 DEBUG 8972 --- [DefaultMessageListenerContainer-1] o.s.j.connection.JmsTransactionManager : Initiating transaction rollback
2017-05-24 09:51:59.868 DEBUG 8972 --- [DefaultMessageListenerContainer-1] o.s.j.connection.JmsTransactionManager : Rolling back JMS transaction on Session [ActiveMQSession {id=ID:D6C0B8467A518-58248-1495590693980-1:32:1,started=true} java.lang.Object#65d647cd]
According to ActiveMQ message re-delivery documentation, the messages, which failed to be delivered, will go to the dead letter queue (http://activemq.apache.org/message-redelivery-and-dlq-handling.html):
"The default Dead Letter Queue in ActiveMQ is called ActiveMQ.DLQ; all un-deliverable messages will get sent to this queue and this can be difficult to manage. So, you can set an individualDeadLetterStrategy in the destination policy map of the activemq.xml configuration file, which allows you to specify a specific dead letter queue prefix for a given queue or topic. You can apply this strategy using wild card if you like so that all queues get their own dead-letter queue, as is shown in the example below"
Please, extend you activemq.xml with individualDeadLetterStrategy to the queue.

Resources