Read and Write in IBM MQ with Java Message Service
IBM MQ is a messaging middleware that facilitates communication between different systems using a queue-based architecture. Java Message Service (JMS) is a common API for accessing message-oriented middleware, including IBM MQ. In this article, we will create a simple JMS application for reading and writing messages using IBM MQ.
1. What is MQ?
MQ, or Message Queue, is a technology that enables asynchronous communication between different software system components. It lets applications send and receive messages through a queue, making it easier for senders and receivers to work independently. Key features of MQ include:
- Asynchronous Communication: Producers can send messages to a queue without waiting for the consumer to process them, allowing for non-blocking operations.
- Reliability: MQ systems often include features like message persistence, ensuring that messages are not lost even if the system crashes.
- Scalability: Applications can scale independently by adding more producers or consumers without impacting each other, making it suitable for distributed systems and microservices architectures.
- Load Balancing: Messages can be distributed across multiple consumers, enabling efficient load balancing and improved performance.
1.1 Why Use Message Queues?
Message queues provide a reliable way to decouple different parts of a system, allowing them to communicate asynchronously. This means that a producer can send a message to a queue, and the consumer can process it later, ensuring non-blocking communication between services. This is useful for:
- High Availability Applications: Message queues ensure that messages are not lost if the consumer is unavailable, enhancing fault tolerance.
- Scalable Systems: By decoupling components, we can scale different parts of the system independently.
- Distributed Applications: In microservices or cloud-based architectures, message queues enable communication between services running on different servers or environments.
- Event-Driven Architectures: Applications that rely on events, like order processing systems or IoT networks, can use message queues to handle events in real-time or batch processes.
Message queues help manage high-throughput and fault-tolerant messaging, making them a critical component of modern distributed systems.
2. Running IBM MQ
Using Docker Compose simplifies the management of multi-container applications by allowing us to define and run all our services in a single configuration file. Here is an example of a docker-compose.yml
file to run IBM MQ with a basic configuration:
version: '3.8' services: ibm-mq: image: ibmcom/mq:latest container_name: ibm-mq environment: - LICENSE=accept - MQ_QMGR_NAME=QM1 # Queue Manager name ports: - "1414:1414" # Default port for IBM MQ - "9443:9443" # Web console port volumes: - mq-data:/var/mqm # Persist MQ data across container restarts networks: - mq-network volumes: mq-data: driver: local networks: mq-network: driver: bridge
Here’s an explanation of the Docker Compose file:
- Image: We are using the official IBM MQ image (
ibmcom/mq
). - Environment Variables:
LICENSE=accept
accepts the IBM MQ license.MQ_QMGR_NAME
sets the name of the Queue Manager (QM1
).
- Ports:
- Port
1414
is for the MQ protocol, used by JMS clients. - Port
9443
exposes the IBM MQ web console for administration.
- Port
- Volumes:
mq-data
stores persistent MQ data across container restarts. - Network: We define a custom Docker bridge network (
mq-network
) for communication between services.
After saving the docker-compose.yml
file, we can start IBM MQ with the following command:
docker-compose up -d
This will pull the IBM MQ Docker image, start the container, and expose the necessary ports. We can access the IBM MQ web console by navigating to https://localhost:9443
in our browser using the default credentials:
- Password:
passw0rd
- Username:
admin
2.1 Adding IBM MQ Client to a Java Project
To interact with the IBM MQ server, we need to add the IBM MQ JMS client library to our Java project. Add the following dependencies to your pom.xml
file if you are using Maven:
<dependencies> <!-- IBM MQ JMS Client --> <dependency> <groupId>com.ibm.mq</groupId> <artifactId>com.ibm.mq.allclient</artifactId> <version>9.4.0.0</version> <!-- Ensure the version is up to date --> </dependency> <!-- JMS/Jakarta Messaging API --> <dependency> <groupId>jakarta.jms</groupId> <artifactId>jakarta.jms-api</artifactId> <version>3.1.0</version> </dependency> </dependencies>
3. Configuring Java JMS to Connect to IBM MQ
To interact with the queue, we need to configure a connection factory. Here is an example of setting up a connection to the IBM MQ server running in Docker.
public class IbmJmsExample { private static final String HOSTNAME = "localhost"; private static final int PORT = 1414; private static final String CHANNEL = "DEV.APP.SVRCONN"; private static final String QUEUE_MANAGER = "QM1"; private static final String QUEUE_NAME = "DEV.QUEUE.1"; public static ConnectionFactory createConnectionFactory() throws JMSException { MQConnectionFactory connectionFactory = new MQConnectionFactory(); connectionFactory.setHostName(HOSTNAME); connectionFactory.setPort(PORT); connectionFactory.setChannel(CHANNEL); connectionFactory.setQueueManager(QUEUE_MANAGER); return connectionFactory; } }
Here, we create a MQConnectionFactory
and set the hostname, port, channel and queue manager.
4. Sending a Message
In the Jakarta Messaging API, sending a message involves creating a connection to the message broker, establishing a session, and producing the message to a specified destination (such as a queue or topic). This flexible process allows different types of messages like text, object, byte, or map messages to be sent. The following example demonstrates how to send an object message to an IBM MQ queue.
public class MessageSender { private static final String HOSTNAME = "localhost"; private static final int PORT = 1414; // Default MQ port private static final String CHANNEL = "DEV.APP.SVRCONN"; private static final String QUEUE_MANAGER = "QM1"; private static final String QUEUE_NAME = "DEV.QUEUE.1"; public static ConnectionFactory createConnectionFactory() throws JMSException { MQConnectionFactory connectionFactory = new MQConnectionFactory(); connectionFactory.setHostName(HOSTNAME); connectionFactory.setPort(PORT); connectionFactory.setChannel(CHANNEL); connectionFactory.setQueueManager(QUEUE_MANAGER); connectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT); // Client mode return connectionFactory; } public static void sendObjectMessage(ConnectionFactory connectionFactory) throws JMSException { Connection connection = null; Session session = null; try { connection = connectionFactory.createConnection(); session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); // Create a queue Queue queue = session.createQueue(QUEUE_NAME); // Create a producer MessageProducer producer = session.createProducer(queue); // Create and send an ObjectMessage Employee employee = new Employee("Mr Fish", 1981); ObjectMessage objectMessage = session.createObjectMessage(employee); producer.send(objectMessage); System.out.println("ObjectMessage sent to queue " + "Name: " + employee.getName() + " ID: " + employee.getId()); } catch (JMSException e) { } finally { if (session != null) { session.close(); } if (connection != null) { connection.close(); } } } public static void main(String[] args) throws JMSException { ConnectionFactory connectionFactory = createConnectionFactory(); sendObjectMessage(connectionFactory); } }
This code demonstrates how to send an ObjectMessage
containing an Employee
object to a JMS queue using a ConnectionFactory
. It first establishes a connection and creates a session with automatic acknowledgment mode. A queue is then created using the session, followed by a MessageProducer
responsible for sending messages. An Employee
object is instantiated and wrapped into an ObjectMessage
, which is sent to the queue. Finally, the session and connection are closed to ensure resources are released properly.
Output:
ObjectMessage sent to queue Name: Mr Fish ID: 1981
5. Receiving a Message
In the Jakarta Messaging API, receiving a message from a queue involves creating a connection to the message broker, setting up a session, and consuming messages from the desired destination (queue or topic). The following example demonstrates how to receive and process a message from an IBM MQ queue, focusing on handling ObjectMessage
.
public class MessageReceiver { private static final String HOSTNAME = "localhost"; private static final int PORT = 1414; // Default MQ port private static final String CHANNEL = "DEV.APP.SVRCONN"; private static final String QUEUE_MANAGER = "QM1"; private static final String QUEUE_NAME = "DEV.QUEUE.1"; public static ConnectionFactory createConnectionFactory() throws JMSException { MQConnectionFactory connectionFactory = new MQConnectionFactory(); connectionFactory.setHostName(HOSTNAME); connectionFactory.setPort(PORT); connectionFactory.setChannel(CHANNEL); connectionFactory.setQueueManager(QUEUE_MANAGER); connectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT); // Client mode return connectionFactory; } public static void receiveObjectMessage(ConnectionFactory connectionFactory) throws JMSException { Connection connection = null; Session session = null; try { connection = connectionFactory.createConnection(); session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); Queue queue = session.createQueue(QUEUE_NAME); MessageConsumer consumer = session.createConsumer(queue); connection.start(); // Continuously receive messages from the queue Message message; while ((message = consumer.receive(1000)) != null) { if (message instanceof ObjectMessage) { ObjectMessage objectMessage = (ObjectMessage) message; Employee employee = (Employee) objectMessage.getObject(); System.out.println("Received ObjectMessage: " + employee); } else { System.out.println("Received non-ObjectMessage: " + message); } } System.out.println("No more messages in the queue."); } catch (JMSException e) { } finally { // Ensure session and connection are closed to prevent lingering threads if (session != null) { session.close(); } if (connection != null) { connection.close(); } } } public static void main(String[] args) throws JMSException { ConnectionFactory connectionFactory = createConnectionFactory(); //sendObjectMessage(connectionFactory); receiveObjectMessage(connectionFactory); } }
The receiveObjectMessage
method creates a JMS connection and a session with automatic acknowledgment mode, then defines the queue from which messages will be consumed. A MessageConsumer
is created to listen for messages on that queue, and the connection is started. Inside a loop, it continuously receives messages with a 5-second timeout. When an ObjectMessage
is received, it is cast to an Employee
object, which is then printed.
When you run the above code, it listens to the specified JMS queue (QUEUE_NAME
) and continuously tries to receive messages from it. Assuming the queue contains some messages, the output might look like this:
Received ObjectMessage: Employee{name=Mr Fish, id=1981} Received ObjectMessage: Employee{name=Mr Brown, id=1982} Received ObjectMessage: Employee{name=Mr Pink, id=1983} Received ObjectMessage: Employee{name=Mrs Blue, id=1984} No more messages in the queue.
6. Message Headers and Properties
Every JMS message consists of three parts: headers, properties, and the message body. JMS defines a set of standard message headers that are automatically included with every message. These headers provide information about the message, such as its destination and its delivery status. Some of the key headers include:
- JMSMessageID: A unique identifier assigned to each message when it is sent.
- JMSDestination: Specifies the destination (queue or topic) to which the message is sent.
- JMSExpiration: Indicates the expiration time for a message, after which it will no longer be delivered.
- JMSTimestamp: The time when the message was sent, automatically set by the producer.
- JMSPriority: Defines the priority of the message, ranging from 0 (lowest) to 9 (highest).
In addition to headers, we can define custom properties for each message. Properties are used for application-specific metadata, which can be useful for filtering messages using message selectors or adding additional context to the message. Custom properties can be added using the setStringProperty
, setIntProperty
, or other setXProperty
methods available in the Message
interface.
ObjectMessage objectMessage = session.createObjectMessage(employee); // Set custom properties objectMessage.setStringProperty("SenderName", "Java Code Geeks"); objectMessage.setIntProperty("MessagePriority", 4); objectMessage.setLongProperty("Timestamp", System.currentTimeMillis()); producer.send(objectMessage);
In this code, the MessageSender
class has been updated to include custom properties (SenderName
, MessagePriority
, and Timestamp
), which are set before sending the message to the queue.
6.1 Receiving Messages and Retrieving Headers and Properties
After successfully receiving messages, we can access the custom headers and properties set during message creation. The following code demonstrates how to access these custom headers and properties for each received message.
Message message; while ((message = consumer.receive(1000)) != null) { if (message instanceof ObjectMessage) { ObjectMessage objectMessage = (ObjectMessage) message; Employee employee = (Employee) objectMessage.getObject(); // Retrieve custom properties String senderName = objectMessage.getStringProperty("SenderName"); int messagePriority = objectMessage.getIntProperty("MessagePriority"); long timestamp = objectMessage.getLongProperty("Timestamp"); String messageId = message.getJMSMessageID(); System.out.println("Received ObjectMessage: " + employee); System.out.println("Sender Name: " + senderName); System.out.println("Message Priority: " + messagePriority); System.out.println("Timestamp: " + timestamp); System.out.println("Message ID: " + messageId); }
7. Conclusion
In this article, we explored how to utilize the Java Messaging Service (JMS) with IBM MQ to send and receive messages, specifically focusing on ObjectMessages. We demonstrated how to set up IBM MQ in a Docker container and configure a simple JMS application to interact with message queues.
8. Download the Source Code
This article covers how to read and write messages using Java Message Service (JMS) with IBM MQ.
You can download the full source code of this example here: java message service ibm mq read write