Java Messaging Service (JMS) Queue is a key component in application-to-application messaging, facilitating asynchronous communication. It ensures reliable message delivery and decouples message producers from consumers in a Point-to-Point model.

JMS Queue supports reliable and asynchronous messaging, making it ideal for Enterprise Application Integration and B2B projects. It uses load-balancing techniques to evenly distribute messages among consumers, enhancing scalability and performance.

Learn how to set up a JMS Queue in 10 straightforward steps. Use cases include deploying Queue Sender and Asynchronous Queue Receiver, and managing multiple consumers effectively within a JMS Queue environment.

What Is Message-Oriented Middleware (MOM)?

  • Application-to-application communication hasn’t always been the same. Traditionally, an application would perform an action and in turn trigger a number of resource requests.
  • Upon request, another application would execute a trailing action and answer the queries. Such communication is referred to as Synchronous Communication.
  • But recent developments in application communication have led to Asynchronous Messaging (loosely coupled applications), a communication mode where two applications can communicate without the requirement of being “present” at the same time.
  • Asynchronous Messaging provides convenience and flexibility for both the message consumer and the message producer. It provides application users with remedies to reduce system bottlenecks, and increase end-user productivity and overall system scalability.

In business, asynchronous communication systems between applications are generally referred to as enterprise messaging systems, or Message-Oriented Middleware (MOM). JMS is one example of MOM that we’ll discuss in this blog.

MOM transmits messages from one application to another across a network and ensures that messages are properly distributed. This increases the overall scalability and throughput of a system while accelerating end-user productivity.

What Is Java Message Service (JMS)?

  • Java Message Service (JMS) is a Java API designed for asynchronous messaging in Java applications, offering portability, reliability, scalability, and transactional support. Similar to JDBC, it allows developers to create, send, receive, and manage distributed enterprise messages across different application systems.
  • JMS provides a robust platform for Java Client Applications and Middle-Tier Services, unifying them with a common set of interfaces and semantics. This compatibility extends to messaging implementations in other programming languages.
  • The JMS API is easy to use with minimal learning curve, yet it supports advanced messaging functionalities required for sophisticated messaging systems.

Java Message Service Architecture

Java Message Service application consists of the following components:

  1. JMS Clients: Java components or applications use the JMS API and JMS Provider to send and receive messages.
  2. Non-JMS Clients: Programs that use a message system’s native client API instead of JMS.
  3. Messages: Package of business data that is sent by the sender (producer application) to the receiver (consumer application). The JMS Queue offers six message interface types: TextMessage, StreamMessage, MapMessage, ObjectMessage, and BytesMessage.
  4. JMS Provider: A Messaging System that implements the JMS API and other administrative and control functionality to provide connectivity to its MOM.
  5. Administered Objects: Preconfigured JMS objects developed by the administrator for the use of Java programs. These are of two types:
    1. ConnectionFactory – An object that a client uses to create a connection with a provider.  
    2. Destination – An object that a client uses to specify the destination and the source of messages.

Merging all the concepts and components from above, we get interaction as follows: 

Administrative Tools allow you to bind destinations and connection factories into a Java Naming and Directory Interface (JNDI) API namespace. A JMS Client then looks up the administered objects in the namespace and then establishes a logical connection to the same objects using the JMS Provider.

When Can You Use the JMS API?

Your decision to choose a messaging API like JMS over a tightly coupled API like Remote Procedure Call (RPC) is right when: 

  • You want your components not to depend on information about other components’ interfaces.
  • You want your applications to run whether their components are up and running simultaneously.
  • Likewise, you want an application business model that allows a component to send information to another and to continue to operate without receiving an immediate response.

Benefits of Java Message Service API (JMS)

  • Easy to Learn and Use: JMS APIs are quick to learn and simple to use for developers.
  • Asynchronous Processing: JMS delivers asynchronous messaging between applications. Once a message is sent, the JMS Client can move on to other tasks. It doesn’t have to wait for a response. 
  • High Availability: JMS provides high availability capabilities to keep business applications in continuous operation with uninterrupted service.
  • Reliable Messaging: JMS provides guaranteed delivery, which ensures that intended consumers will eventually receive a message even if a partial failure occurs.
  • Store-and-Forward: JMS writes incoming messages to a persistent store if the intended consumers are not currently available.

The Java Message Service Programming Model

In a broad sense, a JMS application is one or more JMS Clients that exchange messages. Non-JMS Clients can be involved in the application, but they will utilize the JMS provider’s native API instead of JMS. Between these applications are self-contained packages of business data that get exchanged, and each JMS message comprises the following components:

  • Header: The header declares the attributes of a message and provides information for routing.
  • Properties (optional): The properties segment contains additional metadata about the message that is set by the application developer or JMS Provider. Each message can have three different properties:
    • Application-Specific Properties: Specify application-specific header fields to a message.
    • Standard Properties: Standard and optional properties specified by the JMS.
    • Provider-Specific Properties: Specify provider-specific properties for integrating a JMS client with a JMS provider native client.
  • Body (optional): The body contains the actual data to communicate. 

The JMS defines six types of messages that a JMS Provider must support:

  1. Message: The simplest type of message that lacks a body.
  2. StreamMessage: A message whose body carries a stream of primitive Java types.
  3. MapMessage: A message whose body contains a set of name-value pairs. The order of entries is not defined.
  4. TextMessage: A message whose body carries java.lang.String.
  5. ObjectMessage: A message that carries a serializable Java object.
  6. BytesMessage: A message whose body contains an array of primitive bytes.

Java Message Service: Message Styles

JMS supports two styles of messaging: 

JMS Point-to-Point (PTP) Messaging Model

  • In this Point-to-Point (PTP) Messaging Model, Java programs use JMS Queues to send and receive messages, where message producers are “senders” and message receivers are “consumers.”
  • Each message in a JMS Queue is delivered to only one consumer application, even if multiple consumers are attached to the queue.
  • In Pull-Based Model, Consumers must request messages from the JMS Queue, supporting both synchronous “request/reply” and asynchronous “fire and forget” messaging without timing dependencies between sender and consumer.
  • The sender must know how and by whom the message will be used in Coupled Connection. The receiver can fetch messages regardless of its runtime status when the sender sends the message and acknowledges successful processing for removal from the queue.
  • JMS Queue Browser allows a JMS Client to view the contents of a Queue before consuming the messages, enabling the browsing of messages and displaying their header values.

JMS Publish-and-Subscribe (Pub/Sub) Messaging Using Topics

  • Unlike the JMS Queue Model, where each message is consumed by only one receiver, the Publish-and-Subscribe Messaging Model delivers one message to multiple subscribers via a Topic.
  • Publish-and-Subscribe automatically delivers messages to all subscribers without them needing to request or poll for messages, providing a push-based approach.
  • In Publish-and-Subscribe, producers do not need to know about the subscribers, allowing for higher decoupling compared to the PTP Messaging Model, which requires the sender to know the receiver’s address.

Steps to Create a JMS Queue

A JMS Queue is a point-to-point destination type for a JMS Server. Once you have configured your JMS Server, you can create one or more Queues for your JMS Server. Provided here are the steps to create one:

Step 1: Expand JMS > Servers and select a JMS Server instance.

Step 2: Select the Destination node. On the right pane, you’ll see the JMS Destinations table showing all the JMS Queues.

Step 3: Click on Configure a new JMS Queue. The tabs for configuring a new JMS Queue will be displayed in the dialog box.

Step 4: Under the Configuration General tab, define the Queue’s general configuration attributes like:

  • Name of the JMS Queue destination.
  • JNDI name.
  • Specify if the JNDI name is to be replicated across the cluster.
  • Set whether the JMS Queue should support persistent messaging.
  • If you’re creating Queues using JMS, choose an existing template.
  • Pick from a list of existing destination keys to determine the sort order of messages as they arrive in the Queue.

Step 5: Click Create to establish a JMS Queue instance with the name you entered in the Name field. On the left pane’s Destination node, you’ll see the new instance added.

Step 6: Define the following higher and lower message/byte thresholds and maximum quota characteristics for the JMS Queue on the Configuration Thresholds & Quotas tab:

  • Maximum bytes or message quota for your JMS Queue.
  • The upper threshold value and the lower threshold value to trigger events based on the number of bytes or messages stored in the JMS Queue.
  • Enable/disable bytes or messages paging.
  • Maximum acceptable size of a message.

Step 7: Set the message attributes that a message producer can override from the Configuration Overrides tab, such as priority, time-to-live, time-to-deliver, and delivery mode.

Step 8: Define the message redelivery attributes on the Configuration Redelivery tab. This includes redelivery delay override, redelivery limit, and error destination.

Step 9: Set the message expiration policy on the Configuration Expiration Policy tab.

Step 10: Click Apply to save your changes.

JMS Queue Sample Applications

There are four sample programs in this section.

1. Simple JMS Queue Application

Here’s a simple JMS Queue application that sends and receives text messages using a point-to-point connection, i.e., using JMS Queues. The sample application serves as both a sender (sending the message to the Queue) and a receiver in this example (receiving the same message from the Queue).

import javax.jms.*;

public class HelloMsg {
   public static void main(String argv[]) throws Exception {
      // The producer and consumer need to get a connection factory and use it to set up
      // a connection and a session
      QueueConnectionFactory connFactory = new com.sun.messaging.QueueConnectionFactory();
      QueueConnection conn = connFactory.createQueueConnection();
      // This session is not transacted, and it uses automatic message acknowledgement
      QueueSession session = conn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
      Queue q = new com.sun.messaging.Queue("world");
      // Sender
      QueueSender sender = session.createSender(q);
      // Text message
      TextMessage msg = session.createTextMessage();
      msg.setText("Hello there!");
      System.out.println("Sending the message: "+msg.getText());
      sender.send(msg);
      // Receiver
      QueueReceiver receiver = session.createReceiver(q);
      conn.start();
      Message m = receiver.receive();
      if(m instanceof TextMessage) {
         TextMessage txt = (TextMessage) m;
         System.out.println("Message Received: "+txt.getText());
      }
      session.close();
      conn.close();
   }
}

If you would like to experiment with the sample JMS Queue application, you can do so by:

  • Copying the HelloMsg class and saving it in a new file, HelloMsg.java.
  • Compiling HelloMsg.java using javac -classpath <MQ_INSTALL_DIR>/lib/jms.jar{;|:}<MQ_INSTALL_DIR>/lib/img.jar HelloMsg.java.
  • Running HelloMsg as follows java -cp <MQ_INSTALL_DIR>/lib/jms.jar{;|:};<MQ_INSTALL_DIR>/lib/img.jar HelloMsg HelloMsg.

When you run the code, you should get the following output:

Sending the message: Hello there!
Message Received: Hello there!

JMS gives you options to build a unique setup by configuring the JMS source and receiver independently. We discuss two more sample programs to configure a JMS Sender and Asynchronous Queue Receiver. 

2. JMS Queue Sender

To configure a JMS Queue Sender, you can execute the following code and deploy your sender application.

package pointToPoint;
                                                                           
import javax.naming.InitialContext;
                                                                           
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.QueueSender;
import javax.jms.DeliveryMode;
import javax.jms.QueueSession;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
                                                                           
public class Sender
{
    public static void main(String[] args) throws Exception
    {
    |   // get the initial context
    |   InitialContext ctx = new InitialContext();
    |                                                                      
    |   // lookup the queue object
    |   Queue queue = (Queue) ctx.lookup("queue/queue0");
    |                                                                      
    |   // lookup the queue connection factory
    |   QueueConnectionFactory connFactory = (QueueConnectionFactory) ctx.
    |       lookup("queue/connectionFactory");
    |                                                                      
    |   // create a queue connection
    |   QueueConnection queueConn = connFactory.createQueueConnection();
    |                                                                      
    |   // create a queue session
    |   QueueSession queueSession = queueConn.createQueueSession(false,
    |       Session.DUPS_OK_ACKNOWLEDGE);
    |                                                                      
    |   // create a queue sender
    |   QueueSender queueSender = queueSession.createSender(queue);
    |   queueSender.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
    |                                                                      
    |   // create a simple message to say "Hello"
    |   TextMessage message = queueSession.createTextMessage("Hello");
    |                                                                      
    |   // send the message
    |   queueSender.send(message);
    |                                                                      
    |   // print what we did
    |   System.out.println("sent: " + message.getText());
    |                                                                      
    |   // close the queue connection
    |   queueConn.close();
    }
}

This code uses a NON_PERSISTENT message delivery mode. In a non-persistent mode, a JMS Client can tolerate lost messages if the JMS Queue broker fails. In a persistent message delivery mode, a JMS Client cannot function if the messages are lost in transit.

3. Asynchronous Queue Receiver

Here’s another example of configuring an Asynchronous Queue Receiver. After receiving the message from the sender, an Asynchronous Queue Receiver does all message distribution tasks without requiring any involvement of the sender application. To deploy an Asynchronous Queue Receiver, you can use the following code:

package pointToPoint;
                                                                           
import javax.naming.InitialContext;
                                                                           
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.Message;
import javax.jms.TextMessage;
import javax.jms.MessageListener;
import javax.jms.JMSException;
import javax.jms.ExceptionListener;
import javax.jms.QueueSession;
import javax.jms.QueueReceiver;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
                                                                           
public class AsyncReceiver implements MessageListener, ExceptionListener
{
    public static void main(String[] args) throws Exception
    {
    |   // get the initial context
    |   InitialContext ctx = new InitialContext();
    |                                                                      
    |   // lookup the queue object
    |   Queue queue = (Queue) ctx.lookup("queue/queue0");
    |                                                                      
    |   // lookup the queue connection factory
    |   QueueConnectionFactory connFactory = (QueueConnectionFactory) ctx.
    |       lookup("queue/connectionFactory");
    |                                                                      
    |   // create a queue connection
    |   QueueConnection queueConn = connFactory.createQueueConnection();
    |                                                                      
    |   // create a queue session
    |   QueueSession queueSession = queueConn.createQueueSession(false,
    |       Session.AUTO_ACKNOWLEDGE);
    |                                                                      
    |   // create a queue receiver
    |   QueueReceiver queueReceiver = queueSession.createReceiver(queue);
    |                                                                      
    |   // set an asynchronous message listener
    |   AsyncReceiver asyncReceiver = new AsyncReceiver();
    |   queueReceiver.setMessageListener(asyncReceiver);
    |                                                                      
    |   // set an asynchronous exception listener on the connection
    |   queueConn.setExceptionListener(asyncReceiver);
    |                                                                      
    |   // start the connection
    |   queueConn.start();
    |                                                                      
    |   // wait for messages
    |   System.out.print("waiting for messages");
    |   for (int i = 0; i < 10; i++) {
    |   |   Thread.sleep(1000);
    |   |   System.out.print(".");
    |   }
    |   System.out.println();
    |                                                                      
    |   // close the queue connection
    |   queueConn.close();
    }
                                                                           
    /**
      This method is called asynchronously by JMS when a message arrives
      at the queue. Client applications must not throw any exceptions in
      the onMessage method.
      @param message A JMS message.
     */
    public void onMessage(Message message)
    {
    |   TextMessage msg = (TextMessage) message;
    |   try {
    |   |   System.out.println("received: " + msg.getText());
    |   } catch (JMSException ex) {
    |   |   ex.printStackTrace();
    |   }
    }
                                                                           
    /**
      This method is called asynchronously by JMS when some error occurs.
      When using an asynchronous message listener it is recommended to use
      an exception listener also since JMS has no way to report errors
      otherwise.
      @param exception A JMS exception.
     */
    public void onException(JMSException exception)
    {
    |   System.err.println("an error occurred: " + exception);
    }
}

4. Multiple Consumers in JMS Queue

When you have multiple consumers, you can distribute the workload for effective and timely processing of messages. In such scenarios, each message in the Queue is delivered to one of the receivers. Because one receiver may process messages quicker than another, the absolute sequence of messages cannot be guaranteed. Once consumed and acknowledged, the message is deleted from the JMS Queue.

To deploy multiple consumer processing on a JMS Queue, you can use the following code:

package com.javacodegeeks.jms;
 
import java.net.URI;
import java.net.URISyntaxException;
 
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
 
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerFactory;
import org.apache.activemq.broker.BrokerService;
 
public class JmsMultipleCustomersMessageQueueExample {
    public static void main(String[] args) throws URISyntaxException, Exception {
        BrokerService broker = BrokerFactory.createBroker(new URI(
                "broker:(tcp://localhost:61616)"));
        broker.start();
        Connection connection = null;
        try {
            // Producer
            ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
                    "tcp://localhost:61616");
            connection = connectionFactory.createConnection();
            Session session = connection.createSession(false,
                    Session.AUTO_ACKNOWLEDGE);
            Queue queue = session.createQueue("customerQueue");
 
            // Consumer
            for (int i = 0; i < 4; i++) {
                MessageConsumer consumer = session.createConsumer(queue);
                consumer.setMessageListener(new ConsumerMessageListener(
                        "Consumer " + i));
            }
            connection.start();
 
            String basePayload = "Important Task";
            MessageProducer producer = session.createProducer(queue);
            for (int i = 0; i < 10; i++) {
                String payload = basePayload + i;
                Message msg = session.createTextMessage(payload);
                System.out.println("Sending text '" + payload + "'");
                producer.send(msg);
            }
 
            Thread.sleep(1000);
            session.close();
        } finally {
            if (connection != null) {
                connection.close();
            }
            broker.stop();
        }
    }
 
}

Code Credits: ​​

Once executed, you will receive an output something like this. The messages you see get ordered in a round-robin fashion.

INFO | PListStore:[C:javacodegeeks_wsjmsMessageTypesExampleactivemq-datalocalhosttmp_storage] started
 INFO | Using Persistence Adapter: KahaDBPersistenceAdapter[C:javacodegeeks_wsjmsMessageTypesExampleactivemq-datalocalhostKahaDB]
 INFO | JMX consoles can connect to service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi
 INFO | KahaDB is version 6
 INFO | Recovering from the journal @1:173161
 INFO | Recovery replayed 1 operations from the journal in 0.012 seconds.
 INFO | Apache ActiveMQ 5.12.0 (localhost, ID:INMAA1-L1005-62099-1446469937715-0:1) is starting
 INFO | Listening for connections at: tcp://127.0.0.1:61616
 INFO | Connector tcp://127.0.0.1:61616 started
 INFO | Apache ActiveMQ 5.12.0 (localhost, ID:INMAA1-L1005-62099-1446469937715-0:1) started
 INFO | For help or more information please see: http://activemq.apache.org
 WARN | Store limit is 102400 mb (current store usage is 0 mb). The data directory: C:javacodegeeks_wsjmsMessageTypesExampleactivemq-datalocalhostKahaDB only has 34555 mb of usable space - resetting to maximum available disk space: 34556 mb
 WARN | Temporary Store limit is 51200 mb, whilst the temporary data directory: C:javacodegeeks_wsjmsMessageTypesExampleactivemq-datalocalhosttmp_storage only has 34555 mb of usable space - resetting to maximum available 34555 mb.
Sending text 'Important Task0'
Consumer 0 received Important Task0
Sending text 'Important Task1'
Consumer 1 received Important Task1
Sending text 'Important Task2'
Consumer 2 received Important Task2
Sending text 'Important Task3'
Consumer 3 received Important Task3
Sending text 'Important Task4'
Consumer 0 received Important Task4
Sending text 'Important Task5'
Consumer 1 received Important Task5
Sending text 'Important Task6'
Consumer 2 received Important Task6
Sending text 'Important Task7'
Consumer 3 received Important Task7
Sending text 'Important Task8'
Consumer 0 received Important Task8
Sending text 'Important Task9'
Consumer 1 received Important Task9
 INFO | Apache ActiveMQ 5.12.0 (localhost, ID:INMAA1-L1005-62099-1446469937715-0:1) is shutting down
 INFO | Connector tcp://127.0.0.1:61616 stopped
 INFO | PListStore:[C:javacodegeeks_wsjmsMessageTypesExampleactivemq-datalocalhosttmp_storage] stopped
 INFO | Stopping async queue tasks
 INFO | Stopping async topic tasks
 INFO | Stopped KahaDB
 INFO | Apache ActiveMQ 5.12.0 (localhost, ID:INMAA1-L1005-62099-1446469937715-0:1) uptime 2.009 seconds
 INFO | Apache ActiveMQ 5.12.0 (localhost, ID:INMAA1-L1005-62099-1446469937715-0:1) is shutdown

Conclusion

  • JMS unifies enterprise messaging concepts and facilities into a single messaging service. It’s handy for asynchronous communication across enterprise applications that don’t need to handle requests at the same time.
  • Furthermore, JMS applications are extremely portable across many machine architectures and operating systems, ensuring that you and your team can collaborate and communicate without difficulty.
Divyansh Sharma
Marketing Research Analyst, Hevo Data

Divyansh is a Marketing Research Analyst at Hevo who specializes in data analysis. He is a BITS Pilani Alumnus and has collaborated with thought leaders in the data industry to write articles on diverse data-related topics, such as data integration and infrastructure. The contributions he makes through his content are instrumental in advancing the data industry.

No Code Data Pipeline For Your Data Warehouse