Using multiple JMS servers with Spring Boot 
Tags: springboot, jms, mq
Published: 2020-08-13 18:16:34

---
title: Using multiple JMS servers with Spring Boot 
published: true
description: 
tags: springboot, jms, mq
//cover_image: https://direct_url_to_image.jpg
---

Spring Boot support for JMS is straightforward to use. But the default configuration is limited to one server.

Let's see how to use more than one server. (In this example I will be using IBM MQ, but the same principle can be applied to other products.)

First we need to add configuration properties for two servers:

    qm1.queueManager=QM1
    qm1.channel=DEV.ADMIN.SVRCONN
    qm1.connName=localhost(1414)
    qm1.user=admin
    qm1.password=passw0rd
    qm1.pool.enabled=true
    
    qm2.queueManager=QM2
    qm2.channel=DEV.ADMIN.SVRCONN
    qm2.connName=localhost(1415)
    qm2.user=admin
    qm2.password=passw0rd
    qm2.pool.enabled=true

In this example we added the "qm1" and "qm2" prefixes to the standard properties, to configure the two servers.

For each one of the servers we need to read these properties (using `@ConfigurationProperties` annotation with the prefix) and create an specific `ConnectionFactory` and `JmsListenerContainerFactory`.

The `Qm1Config` and `Qm2Config` configure these beans.

```java
@Configuration
public class Qm1Config {

    @Bean
    @ConfigurationProperties("qm1")
    public MQConfigurationProperties qm1ConfigProperties() {
        return new MQConfigurationProperties();
    }

    @Bean
    public MQConnectionFactory qm1ConnectionFactory(@Qualifier("qm1ConfigProperties") MQConfigurationProperties properties, ObjectProvider<List<MQConnectionFactoryCustomizer>> factoryCustomizers) {
        return new MQConnectionFactoryFactory(properties, factoryCustomizers.getIfAvailable()).createConnectionFactory(MQConnectionFactory.class);
    }

    @Bean
    public JmsListenerContainerFactory<?> qm1JmsListenerContainerFactory(@Qualifier("qm1ConnectionFactory") ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer) {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        configurer.configure(factory, connectionFactory);
        return factory;
    }

}
```

```java
@Configuration
public class Qm2Config {

    @Bean
    @ConfigurationProperties("qm2")
    public MQConfigurationProperties qm2ConfigProperties() {
        return new MQConfigurationProperties();
    }

    @Bean
    public MQConnectionFactory qm2ConnectionFactory(@Qualifier("qm2ConfigProperties") MQConfigurationProperties properties, ObjectProvider<List<MQConnectionFactoryCustomizer>> factoryCustomizers) {
        return new MQConnectionFactoryFactory(properties, factoryCustomizers.getIfAvailable()).createConnectionFactory(MQConnectionFactory.class);
    }

    @Bean
    public JmsListenerContainerFactory<?> qm2JmsListenerContainerFactory(@Qualifier("qm2ConnectionFactory") ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer) {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        configurer.configure(factory, connectionFactory);
        return factory;
    }

}
```

Then, the listener must specify which queue manager is to be used:

```java
@Component
public class QueueConsumer {

    @JmsListener(destination = "${example.queue}", containerFactory = "qm1JmsListenerContainerFactory")
    public void receive1(String text) {
        System.out.println("Received from qm1: " + text);
    }

    @JmsListener(destination = "${example.queue}", containerFactory = "qm2JmsListenerContainerFactory")
    public void receive2(String text) {
        System.out.println("Received from qm2: " + text);
    }

}
```
And that's it!

### Example

A complete runnable example is available in GitHub:
https://github.com/adzubla/qmgrs

# Dynamic creation of listeners

The above solution hardwires the `JmsListeners` and `ConnectionFactories` in source code. To have a configurable number of queue managers we can use the approach that follows.

The new properties file:

    qm.list.0.queueManager=QM1
    qm.list.0.channel=DEV.ADMIN.SVRCONN
    qm.list.0.connName=localhost(1414)
    qm.list.0.user=admin
    qm.list.0.password=passw0rd
    qm.list.0.pool.enabled=true
    qm.list.1.queueManager=QM2
    qm.list.1.channel=DEV.ADMIN.SVRCONN
    qm.list.1.connName=localhost(1415)
    qm.list.1.user=admin
    qm.list.1.password=passw0rd
    qm.list.1.pool.enabled=true

To read these properties in a `List` of properties, we use the configuration below:

```java
@ConfigurationProperties("qm")
@Configuration
public class QmProperties {

    private List<MQConfigurationProperties> list;

    public List<MQConfigurationProperties> getList() {
        return list;
    }

    public void setList(List<MQConfigurationProperties> list) {
        this.list = list;
    }

}
```
As in the previous example, for each one of the servers we need to create an specific `ConnectionFactory` and `JmsListenerContainerFactory`. This is done using `JmsListenerConfigurer` that allows the creation of listeners programatically.

```java
@Configuration
@EnableJms
public class JmsConfig implements JmsListenerConfigurer {

    @Autowired
    private ObjectProvider<List<MQConnectionFactoryCustomizer>> factoryCustomizers;

    @Autowired
    private DefaultJmsListenerContainerFactoryConfigurer configurer;

    @Autowired
    private QmProperties qmProperties;

    @Autowired
    private QueueConsumer queueConsumer;

    @Value("${example.queue}")
    private String destination;

    @Override
    public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
        for (MQConfigurationProperties properties : qmProperties.getList()) {
            String queueManager = properties.getQueueManager();

            MQConnectionFactory connectionFactory = new MQConnectionFactoryFactory(properties, factoryCustomizers.getIfAvailable()).createConnectionFactory(MQConnectionFactory.class);

            DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
            configurer.configure(factory, connectionFactory);

            SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
            endpoint.setId("jmsEndpoint-" + queueManager);
            endpoint.setDestination(destination);
            endpoint.setMessageListener(message -> {
                try {
                    queueConsumer.receive(queueManager, message.getBody(String.class));
                } catch (JMSException e) {
                    throw new RuntimeException(e);
                }
            });
            registrar.registerEndpoint(endpoint, factory);
        }
    }

}
```

The `QueueConsumer` bean will be registered for each of the queue managers the present in the configuration.

Note that we removed the `@JmsListener` because the `QueueConsumer` bean will be registered by our `JmsConfig`.

```java
@Component
public class QueueConsumer {

    public void receive(String queueManeger, String text) {
        System.out.println("Received from " + queueManeger + ": " + text);
    }

}
```