1

I am trying to send a JMS messages with Spring's JmsTemplate but it fails with this as the root cause shown in the stack trace:

AMQ219007: Cannot connect to server

I can, however, send a message programming directly against the JMS classes using the same connection factory which is passed to JmsTemplate. I'm sure I'm missing something obvious, but I just can't figure out what configuration is missing to make JmsTemplate work. I think the configuration of the ActiveMQInitialContextFactory is correct since I can send messages using the javax.jms.* classes. FWIW, I've tried configuring the JmsTemplate with a destination resolver but that didn't help.

Here is the configuration:

<bean id="artemisJndiTemplate"
        class="org.springframework.jndi.JndiTemplate">
    <property name="environment">
        <props>
            <prop key="java.naming.factory.initial">org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory</prop>
            <prop key="java.naming.provider.url">(tcp://artemisServer1:61616,tcp://artemisServer2:61616)?user=$jmsArtemisSetup{user}&amp;password=$jmsArtemisSetup{password}&amp;jms.prefetchPolicy.all=1&amp;consumerWindowSize=0&amp;clientID=${NODENBRTAG}</prop>
        </props>
    </property>
</bean>

<!-- look up the JMS ConnectionFactory in JNDI -->
<bean id="artemisConnectionFactory"
        class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiTemplate" ref="artemisJndiTemplate" />
    <property name="jndiName" value="ConnectionFactory" />
</bean>

<bean id="qFromSvcToESB"
        class="org.springframework.jms.core.JmsTemplate">
    <property name="connectionFactory" ref="artemisConnectionFactory" />
    <property name="sessionAcknowledgeModeName" value="CLIENT_ACKNOWLEDGE" />
    <property name="sessionTransacted" value="true" />
    <property name="defaultDestination">
        <bean class="org.springframework.jndi.JndiObjectFactoryBean">
            <property name="jndiTemplate" ref="artemisJndiTemplate" />
            <property name="jndiName" value="dynamicQueues/qFromSvcToESB" />
        </bean>
    </property>
</bean>

When I send a message using the JmsTemplate, I get the following stack trace:

2021-08-18 10:06:31,796 [ERROR] [com.example.emailassistant.msghdlr.EmailAssistantMsgHdlr]:171  - 1629306356191 - jmsTemplate.send(...) failed.
org.springframework.jms.UncategorizedJmsException: Uncategorized exception occurred during JMS processing; nested exception is javax.jms.JMSException: Failed to create session factory; nested exception is ActiveMQNotConnectedException[errorType=NOT_CONNECTED message=AMQ219007: Cannot connect to server(s). Tried with all available servers.]
        at org.springframework.jms.support.JmsUtils.convertJmsAccessException(JmsUtils.java:311) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        at org.springframework.jms.support.JmsAccessor.convertJmsAccessException(JmsAccessor.java:185) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        at org.springframework.jms.core.JmsTemplate.execute(JmsTemplate.java:507) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        at org.springframework.jms.core.JmsTemplate.send(JmsTemplate.java:576) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        at org.springframework.jms.core.JmsTemplate.send(JmsTemplate.java:567) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        at com.example.emailassistant.msghdlr.EmailAssistantMsgHdlr.execute(EmailAssistantMsgHdlr.java:151) ~[com.example.emailassistant.msghdlr-0.0.1-SNAPSHOT.jar:?]
        at com.example.framework2.activemq.artemis.EpmServiceHandlerAdapter.dispatchToListener(EpmServiceHandlerAdapter.java:71) ~[com.example.framework2.activemq.artemis-5.1-SNAPSHOT.jar:?]
        at com.example.framework2.AbstractEpmMsgHdlrAdapter$EpmMessageListener.onMessage(AbstractEpmMsgHdlrAdapter.java:615) ~[com.example.framework2-5.1-SNAPSHOT.jar:?]
        at org.apache.activemq.artemis.jms.client.JMSMessageListenerWrapper.onMessage(JMSMessageListenerWrapper.java:110) ~[artemis-jms-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.core.client.impl.ClientConsumerImpl.callOnMessage(ClientConsumerImpl.java:1031) ~[artemis-core-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.core.client.impl.ClientConsumerImpl.access$400(ClientConsumerImpl.java:50) ~[artemis-core-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.core.client.impl.ClientConsumerImpl$Runner.run(ClientConsumerImpl.java:1154) ~[artemis-core-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.utils.actors.OrderedExecutor.doTask(OrderedExecutor.java:42) ~[artemis-commons-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.utils.actors.OrderedExecutor.doTask(OrderedExecutor.java:31) ~[artemis-commons-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.utils.actors.ProcessorBase.executePendingTasks(ProcessorBase.java:65) ~[artemis-commons-2.12.0.jar:2.12.0]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
        at org.apache.activemq.artemis.utils.ActiveMQThreadFactory$1.run(ActiveMQThreadFactory.java:118) [artemis-commons-2.12.0.jar:2.12.0]
Caused by: javax.jms.JMSException: Failed to create session factory
        at org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory.createConnectionInternal(ActiveMQConnectionFactory.java:886) ~[artemis-jms-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory.createConnection(ActiveMQConnectionFactory.java:299) ~[artemis-jms-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory.createConnection(ActiveMQConnectionFactory.java:294) ~[artemis-jms-client-2.12.0.jar:2.12.0]
        at org.springframework.jms.support.JmsAccessor.createConnection(JmsAccessor.java:196) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        at org.springframework.jms.core.JmsTemplate.execute(JmsTemplate.java:494) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        ... 15 more
Caused by: org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException: AMQ219007: Cannot connect to server(s). Tried with all available servers.
        at org.apache.activemq.artemis.core.client.impl.ServerLocatorImpl.createSessionFactory(ServerLocatorImpl.java:690) ~[artemis-core-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory.createConnectionInternal(ActiveMQConnectionFactory.java:884) ~[artemis-jms-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory.createConnection(ActiveMQConnectionFactory.java:299) ~[artemis-jms-client-2.12.0.jar:2.12.0]
        at org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory.createConnection(ActiveMQConnectionFactory.java:294) ~[artemis-jms-client-2.12.0.jar:2.12.0]
        at org.springframework.jms.support.JmsAccessor.createConnection(JmsAccessor.java:196) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        at org.springframework.jms.core.JmsTemplate.execute(JmsTemplate.java:494) ~[spring-jms-5.2.12.RELEASE.jar:5.2.12.RELEASE]
        ... 15 more

This code and configuration was previously working with the ActiveMQ "classic" connection factory (i.e. org.apache.activemq.jndi.ActiveMQInitialContextFactory).

Here is the destination resolver configuration I tried. I added it to the JmsTemplate config using <property name="destinationResolver" ref="artemisDestinationResolver" />:

<bean id="artemisDestinationResolver"
        class="org.springframework.jms.support.destination.JndiDestinationResolver">
    <property name="jndiTemplate" ref="amqJndiTemplate" />
    <property name="cache" value="true" />
</bean>
Justin Bertram
  • 29,372
  • 4
  • 21
  • 43
Tim Perry
  • 3,066
  • 1
  • 24
  • 40
  • The server's about page reports it is version 2.10.1. The code is pulling in version 2.12.0 of the artemis libraries (jar files). – Tim Perry Aug 18 '21 at 17:46
  • 1
    It's also worth noting that ActiveMQ Artemis supports the OpenWire JMS client used by ActiveMQ "classic" so it's not strictly necessary to change your client applications to the core JMS client shipped with Artemis. – Justin Bertram Aug 18 '21 at 18:18
  • That is true, and that is how we have been running our JMS services for the last year. However, we are having a problem turning off pre-fetch so I was exploring changing over to the JMS libraries from Artemis which use core. The problem is that we dynamically start nodes to process messages and the first one to start grabbed the majority of messages with failover:(tcp://artemisServer1:61616,tcp://artemisServer2:61616)?jms.watchTopicAdvisories=false,jms.prefetchPolicy.all=1,consumerWindowSize=0,clientID=${AIS_NODENBRTAG} – Tim Perry Aug 18 '21 at 18:22
  • With the 2.18.0 libs, I get "javax.jms.InvalidClientIDException: clientID=Node1 was already set into another connection". I do already have a connection from the factory which is listening for JMS messages so a client with that ID is registered, but I'd expect to be able to connect to send messages.... – Tim Perry Aug 18 '21 at 18:28
  • 1
    It doesn't matter what the connection will be used for. Two connections cannot use the same client ID concurrently. That's a violation of the JMS specification. – Justin Bertram Aug 18 '21 at 18:49
  • So then if the client ID is set in the URL logically there can only be one connection at a time.... So the code should never have worked. Interesting. Any idea how to turn off pre-fetching messages using the OpenWire JMS client used by ActiveMQ "classic"? – Tim Perry Aug 18 '21 at 18:53
  • If you're using the OpenWire client I would expect that turning off prefetch is the same when using classic or Artemis. – Justin Bertram Aug 18 '21 at 19:14
  • Nope, it isn't the same. With OpenWire, I created a prefetch policy object rather than modified the URL. https://stackoverflow.com/questions/18634619/where-to-change-prefetch-value-in-activemq#answer-48704647 – Tim Perry Aug 18 '21 at 19:16
  • The [ActiveMQ classic documentation](http://activemq.apache.org/what-is-the-prefetch-limit-for.html) clearly indicates it can be set on the URL. Of course, you can set it programmatically as well. Either way, the prefetch configuration is a feature of the OpenWire client and that doesn't change whether you're using classic or Artemis as the broker. – Justin Bertram Aug 18 '21 at 19:20
  • Yes, the documentation says that. However, based on running tests, setting it on the URL works with a classic ActiveMQ server but not with ActiveMQ Artemis. Unrelated to that, I really appreciate the time you've put into helping me here. It has been a big help narrowing down what options are available and understanding the issues a bit better. Thank you, Justin. – Tim Perry Aug 18 '21 at 19:28
  • FWIW, you might want to revise your question since much has changed since you created it using 2.10.1. It doesn't reflect your current problem(s) as far as I can tell. – Justin Bertram Aug 18 '21 at 19:28
  • I completely agree. I'll do that. – Tim Perry Aug 18 '21 at 19:32

1 Answers1

1

The root cause of the error is that the application is trying to make two JMS connections to the JMS server with the same client ID. This is not allowed by the JMS specification.

The solution is to use connection pooling. In general, connection pooling should be included for performance reasons. In this particular situation, the application starts up, listens for one message, sends one reply message, and shuts down so pooling was not enabled. However, when using the artemis-jms-client library this meant two different JMS connections were being created with the same client ID leading to a failure.

The following XML configuration using pooling allowing the service to work without error:

<bean id="artemisJndiTemplate"
        class="org.springframework.jndi.JndiTemplate">
    <property name="environment">
        <props>
            <prop key="java.naming.factory.initial">org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory</prop>
            <prop key="java.naming.provider.url">(tcp://artemisServer1:61616,tcp://artemisServer1:61616)?user=$jmsArtemisSetup{user}&amp;password=$jmsArtemisSetup{password}&amp;consumerWindowSize=0&amp;clientID=${NODENBRTAG}</prop>
        </props>
    </property>
</bean>

<!-- look up the JMS ConnectionFactory in JNDI -->
<bean id="artemisConnectionFactoryBase"
        class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiTemplate" ref="artemisJndiTemplate" />
    <property name="jndiName" value="ConnectionFactory" />
</bean>

<!-- A cached connection to wrap the ActiveMQ connection -->
<bean id="artemisConnectionFactory"
    class="org.springframework.jms.connection.CachingConnectionFactory">
    <property name="targetConnectionFactory">
        <ref bean="artemisConnectionFactoryBase" />
    </property>
    <property name="sessionCacheSize">
        <value>100</value>
    </property>
</bean>

<bean id="replyJmsQueue"
        class="org.springframework.jms.core.JmsTemplate">
    <property name="connectionFactory" ref="artemisConnectionFactory" />
    <property name="sessionAcknowledgeModeName" value="CLIENT_ACKNOWLEDGE" />
    <property name="sessionTransacted" value="true" />
    <property name="defaultDestination">
        <bean class="org.springframework.jndi.JndiObjectFactoryBean">
            <property name="jndiTemplate" ref="artemisJndiTemplate" />
            <property name="jndiName" value="dynamicQueues/replyJmsQueue" />
        </bean>
    </property>
</bean>

The raw JMS code and the JmsTemplate both use the connection pool and therefore end up using the same connection.

As an aside, upgrading the artemis-jms-client library from 2.10.1 to 2.18.0 provided a much more useful message in the stack trace than the original.

javax.jms.InvalidClientIDException: clientID=Node1 was already set into another connection

With connection pooling, the JMS code works with either artemis-jms-client 2.10.1 or 2.18.0.

Tim Perry
  • 3,066
  • 1
  • 24
  • 40