Enterprise Java

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.
  • 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
Admin console for managing JMS IBM MQ read and write operations

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.

Download
You can download the full source code of this example here: java message service ibm mq read write

Omozegie Aziegbe

Omos Aziegbe is a technical writer and web/application developer with a BSc in Computer Science and Software Engineering from the University of Bedfordshire. Specializing in Java enterprise applications with the Jakarta EE framework, Omos also works with HTML5, CSS, and JavaScript for web development. As a freelance web developer, Omos combines technical expertise with research and writing on topics such as software engineering, programming, web application development, computer science, and technology.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button