JMS Quickstart

Often you might want to configure a pair of connectors: one to send and one to receive messages to/from a destination. This guide explains how to do this using JMS.

For more complete examples of using connectors to consume and produce messages in general, see Asynchronous Request-Reply.

Step 1: Add connector-jms dependency

The dependency to add to your pom.xml is:

<dependency>
    <groupId>com.iconsolutions.ipf.core.connector</groupId>
    <artifactId>connector-jms</artifactId>
</dependency>

If importing the Icon BOM, or using the Icon BOM as a parent, there’s no need to supply a separate version.

Step 2: Configuration

Connector configuration - in general - is heavily config-driven.The configuration allows us to specify:

  • Queue/Topic names

  • JMS consumer/producer settings

  • Restart settings (on failure)

Here’s an example of a configuration block for a pair of sending and receiving connectors which we will wire into our ConnectorTransport s, for a fictional bank’s booking system which features a request and response topic:

mybank.booking {
  producer { (1)
    queue = "mybank.booking.request" (2)
  }
  consumer { (3)
    queue = "mybank.booking.response" (4)
  }
}
If producing to an IBM MQ queue, the default behaviour is to produce a message with an MQRFH2 header. This can cause issues if the application which consumes the message is a non-JMS application. In such cases specifying queue:///$<queueName>?targetClient=1 will produce the message without the MQRFH2 header. So in the above example, the queue property would be "queue:///mybank.booking.request?targetClient=1" to produce messages without an MQRFH2 header.
For more information see: www.ibm.com/docs/en/ibm-mq/9.3?topic=conversion-jms-message-types
1 This is known as the config root path and will be referenced in the code. It indicates where in the application’s configuration to look for this Send Connector Transport’s settings
2 The queue to send to
3 The config root path to the JMS consumer
4 The queue to receive from

Step 2.1: Common Configuration

By default - the JMS configuration you specify here will fall back to the alpakka.jakarta-jms.consumer and alpakka.jakarta-jms.producer settings.

What this means is that you can simply specify alpakka.jakarta-jms.consumer or alpakka.jakarta-jms.producer settings to globally configure all JMS settings for all JMS consumers and producers. For example:

alpakka.jakarta-jms.consumer.acknowledge-mode = client
alpakka.jakarta-jms.producer.session-count = 10

If - for example - one specific JMS producer or consumer has different settings, it can be configured as such:

alpakka.jakarta-jms.producer.session-count = 10
mybank.booking.producer.session-count = 1

The above configuration means that all JMS producers - apart from the booking producer - will have 10 JMS Session instances, and the booking producer will only have one.

The list of JMS configuration options that can be overridden can be found here (consumer) and here (producer).

Step 3: Create Send Connector Transport

Here’s an example of how a SendConnectorTransport can be created for JMS:

var sendingTransport = JmsConnectorTransport.builder()
        .withName("accounts-booking-send-transport") (1)
        .withActorSystem(actorSystem)
        .withConfigRootPath("mybank.booking.producer") (2)
        .withConnectionFactory(connectionFactory) (3)
        .build();
1 Give the ConnectorTransport a meaningful name
2 Using the mybank.booking.producer configuration key defined in Step 2
3 Provide a JMS ConnectionFactory here

Step 4: Create Receive Connector Transport

Receive Connector Transport Configuration

Two JMS Receive Connector Transport types are available that each support different message acknowledgement requirements:

JmsReceiveConnectorTransport

When using this transport, no message acknowledgements are sent back to the broker, as messages are automatically considered acknowledged upon delivery.

JmsAckReceiveConnectorTransport

This transport uses CLIENT_ACKNOWLEDGEMENT mode, where the receiver explicitly acknowledges messages after they have been processed successfully. It is important to note that acknowledgements in this mode are session-wide: acknowledging one message acknowledges all previously delivered messages in that session. Therefore, at-least-once delivery guarantees require max-pending-acks to be set to 0. With this configuration, increasing the message consumption throughput can be achieved by increasing the session-count property value.

Default JMS Receive Transport configuration

In addition to the common consumer configuration extended from Alpakka (covered in Step 2), the default JMS Receive Transport configuration applies the following overrides:

  default-consumer = {
    connection-retry {
      max-retries = -1
    }
    # https://doc.akka.io/docs/alpakka/current/jms/consumer.html#configure-jms-consumers
    # The sessionCount parameter controls the number of JMS sessions to run in parallel.
    session-count = 5
    # max-pending-acks - Maximum number of acknowledgements queued before they are sent to the broker.
    # at-least-once message delivery guarantees can be achieved by setting max-pending-acks to 0.
    max-pending-acks = 0
    # health check settings for this consumer
    health-check-settings = ${jms.health-check-settings}
  }

Create Receive Connector Transport

Here’s how to create a ConnectorTransport for receiving messages over JMS:

var receivingTransport = JmsReceiveConnectorTransport.builder() (1)
        .withName("accounts-booking-send-transport") (2)
        .withActorSystem(actorSystem)
        .withConfigRootPath("mybank.booking.consumer") (3)
        .withConnectionFactory(connectionFactory) (4)
        .build();
1 You can use JmsReceiveConnectorTransport or JmsAckReceiveConnectorTransport builder here, depending on the required acknowledgement behaviour
2 Give the ConnectorTransport a meaningful name
3 Using the mybank.booking.consumer configuration key defined in Step 2
4 Provide a JMS ConnectionFactory here