Using JMS point-to-point model (queues)
Using JMS queues in Spring Boot is easy.
Just create a JmsTemplate
object and use it to send a message to a queue:
jmsTemplate.convertAndSend("MY.QUEUE", message);
To receive messages from queue, create a message listener:
@JmsListener(destination = "MY.QUEUE")
public void receive(String message) {
...
}
Using JMS pub/sub model (topics)
To use pub/sub model the code is the same as above, you just need to change MY.QUEUE
to MY.TOPIC
and add the following to application.properties
file:
spring.jms.pub-sub-domain=true
But there is a drawback with this solution. As the pub-sub-domain
property is global, all JmsTemplate
s and JmsListener
s will operate with the pub/sub model. You can't use queues and topics in the same application...
Mixing queues and topics
One solution to use both queues and topics in the same Spring Boot application follows.
Subscriber
On the subscriber side you need to create distinct connection factories for queues and topics, setting pubSubDomain
property accordingly:
@Configuration
public class JmsConfig {
@Bean
public JmsListenerContainerFactory<?> queueConnectionFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setPubSubDomain(false);
return factory;
}
@Bean
public JmsListenerContainerFactory<?> topicConnectionFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setPubSubDomain(true);
return factory;
}
}
In JmsListener
annotations you need to specify which factory will be used:
@JmsListener(destination = "MY.QUEUE", containerFactory = "queueConnectionFactory")
public void receiveFromQueue(String text) {
...
}
@JmsListener(destination = "MY.TOPIC", containerFactory = "topicConnectionFactory")
public void receiveFromTopic(String text) {
...
}
Publisher
On the publisher side, the solution is a bit more trickier. For this to work, we assume all topics must have a name ending with ".TOPIC" and we configure a DynamicDestinationResolver
that sets the pubSubDomain
on the destination according to this convention.
@Configuration
public class JmsConfig {
@Bean
public DynamicDestinationResolver destinationResolver() {
return new DynamicDestinationResolver() {
@Override
public Destination resolveDestinationName(Session session, String destinationName, boolean pubSubDomain) throws JMSException {
pubSubDomain = destinationName.endsWith(".TOPIC");
return super.resolveDestinationName(session, destinationName, pubSubDomain);
}
};
}
}
The same JmsTemplate
can be used for both queues and topics because the destination resolver will configure the destination appropriately based on destination name:
jmsTemplate.convertAndSend("MY.QUEUE", message);
jmsTemplate.convertAndSend("MY.TOPIC", message);
Source code
The source code of the complete example is at https://github.com/adzubla/ibm-mq-pubsub
That repository contains a branch named "pubsub-only" with a simpler code that works in pub/sub mode only.
Top comments (2)
Thanks for the article. Just a question: do you know why producers don't work with the same solutions as consumers? I have been searching for an "elegant" solution for some time, but it seems the only way to make it work is using a workaround... JmsTemplate has some glitches :-(
The architecture of the Spring for JMS is quite simple for basic scenarios, but it gets more convoluted for more complex cases...
Besides, I had no problems using JmsTemplate in quite large applications.