Java Message Service (JMS) 1.1 and 2.0 In Depth

Eric J. Bruno
April 2016

JMS is a specification that describes the properties and behavior of an information pipe for Java software. It also describes how Java client applications interact with the information pipe. This article will go into detail on messaging concepts, JMS application implementation, and some available JMS providers. It begins with a brief discussion on various types of inter-component messaging, quickly moving to the concepts of reliable messaging and JMS. Also covered are discussions on the two main revisions of the JMS specification, JMS 1.1 and JMS 2.0 All throughout, critical points will be explained with working code, with the differences between JMS specification revisions compared.

What is Messaging?

The concept of messaging begins with the goal of delivering data. Enterprise messaging forms the communication infrastructure between disparate components and devices in a distributed software system. The important components in a messaging system—producers, consumers, and the messages themselves—are abstracted through the use of interfaces. The result is a set of loosely-coupled components and devices that are part of a cohesive, efficient, reliable IoT system. Components that are loosely-coupled have as few direct interactions with one another as possible. This isolation leads to more robust software, as changes to one part of the system do not ripple through to other parts.

Most messaging systems consist of a broker component that is responsible for delivering messages to and from the various client applications. Messaging brokers generally treat messages as opaque. In fact, the broker doesn’t need to know the purpose or content of a message in order to deliver it. In turn, the software components that send and receive messages don’t need to know how the messages are delivered and, in most cases, which components sent them. The important part is that they are sent and received reliably.

JMS 1.1 has been around for some time, and the API is still in use, but JMS 2.0 is the latest revision of the specification. However, revision 2.0 doesn’t replace revision 1.1 outright. Instead, it simplifies the API in many ways and adds a few new features. Here’s a summary of new features in JMS 2.0:

Additionally, here’s a list of notable API changes in JMS 2.0:

JMS Message Paradigms

Both JMS versions 1.1 and 2.0 support two main message paradigms: point-to-point (or queue-based) messaging, and publish-and-subscribe (or topic-based) messaging. JMS can support the messaging concept of request-and-reply through both of point-to-point and publish-subscribe paradigms.

Request-and-reply messaging is a common form of client-server communication, where a client makes a request to a server, and the server sends back a response. One of the most familiar implementations of this paradigm is the communication between a web browser and a web server (see Figure 1). In this exchange, the client sends a request to the server, and the server responds with the requested data

Figure 1 - Request-and-reply messaging described through the interaction between a Web browser and Web server

Table 1 summarizes the relationships between the common JMS interfaces, and the domain-specific customized interfaces (all of which are explained throughout this article).

JMS Classic
Interfaces
JMS 2.0
Simplified
Point-to-Point
Interfaces
Publish-Subscribe
Interfaces
ConnectionFactory ConnectionFactory ConnectionFactory TopicConnectionFactory
Connection JMSContext QueueConnection TopicConnection
Destination Queue Topic
Session QueueSession TopicSession
MessageProducer JMSProducer QueueSender TopicPublisher
MessageConsumer JMSConsumer QueueReceiver,
QueueBrowser
TopicSubscriber
Table 1 - The JMS common and domain-specific interfaces

Let’s explore point-to-point, or queue-based, messaging in more detail now.

Point-to-Point (queue-based) Messaging

The JMS point-to-point domain, otherwise known as queue-based messaging or store-and-forward, is normally used when off-line message processing is required. The classic example is an email system. If you’re currently logged on and an email arrives, you will see it immediately and be able to read it. If you shut down your system, and are therefore unable to read your email messages as they arrive, they’ll be safely and reliably stored for you to view at a later time (see Figure 2).

Figure 2 - An email inbox is implemented as a store-and-forward queue

An email inbox is basically a queue. When the email reader application is started by the user, the messages that were safely stored in the queue will be delivered. The email application’s display should indicate that new email messages are present, and the user will be able to view them (see Figure 3). Once delivered, the message is removed from the “Inbox” queue. It may be placed on another queue that holds messages that have been read, or it may be discarded altogether. That decision is application-specific, although the mechanics of the queue are the same for all applications.

Figure 3 - When the email reader application starts, messages are delivered from the queue to the reader application

One or more message producers can place messages onto the same queue, and one or more message consumers can listen to a queue to receive messages. However, each message will be delivered to only one consumer (see Figure 4). Having multiple consumers listen to the same queue can help to balance the load of message processing, or ensure that if one consumer fails, there is at least another there to continue processing messages off of the queue. Regardless, the behavior is the same: each message on the queue will be processed exactly once.

Figure 4 - Each queued message is delivered to only one listener

As mentioned earlier, a queue has the added benefit that it stores messages even when there are no listeners available. This feature combined with the queue’s available exactly-once message delivery makes the JMS queue the usual choice for reliable, point-to-point, messaging in distributed enterprise software systems.

In JMS, a queue is represented by the javax.jms.Queue interface, which extends the Destination interface, and it’s either defined by an administrator or by the client application at run time. One or more message producers can place messages onto the same queue, and one or more message consumers can listen to a queue to receive messages. However, each message will be delivered to only one consumer. Having multiple consumers listen to the same queue can help to balance the load of message processing, or ensure that if one consumer fails, there is at least another there to continue processing messages off of the queue. Regardless, the behavior is the same: each message on the queue will be processed exactly once.

A queue has the added benefit that it stores messages even when there are no listeners available. This feature combined with the queue’s exactly-once processing behavior makes the JMS queue the usual choice for reliable, point-to-point, messaging in distributed software systems. Let’s take a look at some code to understand the basics of JMS queue processing.

Sample Email Sender Application

The queue demonstration application we’ll explore includes two JMS client applications. The first one sends simulated email messages to an email “inbox”; the second one reads the email messages off the queue. The EmailSender class in Listing 1 is a message producer that sends messages to a simulated email inbox, which is simply a JMS queue that will be read by the email reader application at a later time.

package emaildemo;
import javax.jms.*;
import javax.naming.*;

public class EmailSender {
    private InitialContext jndi = null;
    private ConnectionFactory conFactory = null;
    private Destination inbox = null;
	
    public EmailSender() {
        try {
            jndi = new InitialContext();
	        
	      // Lookup a JMS connection factory
	      conFactory = (ConnectionFactory)
                jndi.lookup("ConnectionFactory");	
	
            // Get the JMS queue that represents the simulated inbox
            inbox = (Destination)jndi.lookup("Inbox");
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    public void sendEmailv11(Email email) 
      throws NamingException
    { ... }

    public void sendEmailv20(Email email) 
      throws NamingException
    { ... }

    public static void main(String[] args) 
    { ... }
}
Listing 1 - The queue sender class for the simulated email system

The EmailSender class initializes the JMS session in its constructor, and contains one additional method that allows the caller to send email messages to the queue representing the simulated email inbox. Let’s walk through this code in detail.

In the constructor, the first step is to use JNDI (see the next section for more information on JNDI) to get access to the JMS provider’s javax.jms.ConnectionFactory object. The provider-specific JNDI details are typically placed within a configuration file named jndi.properties. The Java VM automatically looks for the presence of this file when you instantiate the InitialContext class.

Java Naming and Directory Interface (JNDI)

JNDI is a naming facility designed to give human readable labels to Java components, or objects. JNDI allows developers to attach their own Java objects to a namespace. It also allows applications to find implementations of particular services based on a standard naming convention.

The Java Message Service (JMS) and other parts of Java EE use the facilities of JNDI to allow client applications to access a provider’s implementation of JMS without knowing the details of the provider itself. For instance, a JMS application can be coded to use JNDI to instantiate an instance of the javax.jms.TopicConnectionFactory class. The application does not need to be coded to use a particular vendor’s JMS Provider. With this abstraction, the client can access JMS objects with generic names, and the JMS provider can supply an implementation for these names. At a later time, if the user of the JMS application decides to use a different JMS Provider, the application will work seamlessly with no code changes. However, this does assume that both JMS Providers adhere to the JNDI specification.

How Does JNDI Work?

JNDI provides a mechanism to map names to Java objects. The code in Listing 2 is a simple example of how JNDI can be used to get an instance of a JMS ConnectionFactory object specifically.

Context jndiContext = new InitialContext();
ConnectionFactory connFactory = 
  (ConnectionFactory)jndiContext.lookup("jms/ConnectionFactory");
Listing 2 - Sample JNDI Code

The first step is to retrieve a JNDI Context object reference. To do this you must provide the URL to the JNDI provider’s server, and the name of the initial context factory. The next step is to instantiate a new InitialContext object with the JNDI provider’s properties. Then, you can use the JNDI Context reference to obtain references to objects in the provider’s object tree via the lookup method. In the case of a JMS Provider, some of the objects in this tree are TopicConnectionFactory, QueueConnectionFactory, Destination, Topic, and Queue.

Once a ConnectionFactory object is obtained, a call to its createConnection method will return a JMS javax.jms.Connection object. This object represents an actual connection to the JMS provider. Because it’s thread-safe, and it consumes a considerable amount of system resources to maintain, your application should require only one active Connection object during its lifetime. Opening multiple connections to a JMS provider is usually unnecessary and a waste of resources.

Next, via a call to the Connection object’s createSession method, the code creates a javax.jms.Session object. A Session object represents a working message session, and can only be used by one thread at a time within your code. Therefore, you should create a Session object for each message producer and consumer thread in your application. The Session object is used as a factory to create javax.jms.Destination, javax.jms.Producer, javax.jms.Consumer, and javax.jms.Message objects.

Continuing through the constructor, the Session object is used to create a reference to the “Inbox” queue by looking it up via JNDI, though it will resort to creating it on the fly if it’s not found. Next, a message producer is created for the “Inbox” queue, which will be used to send simulated email messages. Finally, a call is made to the Connection object’s start method to begin the flow of messages for the queue (and all destinations created through this JMS connection).

The sample application instantiates the EmailSender class and calls its sendEmailv11 method to send an email message to the “Inbox” queue (see Listing 3). First, the code a Connection, and from there creates a Session to the broker. Next, the Session object is used to create a javax.jms.ObjectMessage object that will in turn be used to encapsulate the Email POJO. Finally, after the JMS message is actually sent via a call to the send method on the queue’s Producer object, all of the JMS objects must be closed or resources will be leaked.

// JMS 1.1 send:
public void sendEmailv11(Email email)
  throws NamingException {
    try {
        Connection conn = conFactory.createConnection("","");
        conn.start();
        Session session = conn.createSession( 
              false, Session.AUTO_ACKNOWLEDGE);
        Message msg = session.createObjectMessage(email);
        MessageProducer prod = session.createProducer(inbox);
        prod.send(msg);

        // Must explicitly clean up
        prod.close();
        session.close();
        conn.close();
    }
    catch ( JMSException ex ) {
        ex.printStackTrace();
    }
}

// JMS 2.0 send:
public void sendEmailv20(Email email)
      throws NamingException {
    try (JMSContext context = conFactory.createContext();){
        Message msg = context.createObjectMessage(email);
        context.createProducer().send(inbox, msg);
    }
    catch ( JMSRuntimeException ex ) {
        ex.printStackTrace();
    }
}

public static void main(String[] args) {
    try {
        EmailSender demo = new EmailSender();
			
        demo.sendEmailv11("JMS", 
                          "Article submitted for review");
			
        demo.sendEmailv20("Re: JMS", 
                          "Article revisions available");
    }
    catch ( Exception e ) {
        e.printStackTrace();
    }
}
Listing 3 - Implementing and calling the sendEmail method

Contrast this to the simplified API of JMS 2.0, as illustrated in the method sendEmailv20. Using try-with-resources, a JMSContext object is created, from which a new JMS Message object and Producer are created. Finally, the message is sent via the call to send. In this case, neither a Connection nor a Session needs to be created; it’s simplified and encapsulated with JMS 2.0’s JMSContext object. Note that because of try-with-resources support, are resources are closed automatically Additionally, the Producer created in sendEmailv20 can be used to send messages to other queues or topics; it’s not Destination-specific as in the JMS 1.1 example.

Running the email sender application results in the output shown in Figure 5. Because the simulated email inbox is implemented as a JMS queue, the reader application does not need to be running at the same time as the sender.

Figure 5 - When the email sender application is run, two email messages are placed on the “Inbox” queue

All of the messages sent to the queue will remain in the queue until the reader application executes, and removes them. Let’s take a look at the sample Email reader application now to see how that works.

Sample Email Reader Application

The reader application performs many of the same steps as the sender application in terms of initializing its JMS connection and session. The differences, as shown in Listing 4, are:

package emaildemo;
import javax.jms.*;
import javax.naming.*;

public class EmailReader implements MessageListener {
    final int MODE = 2;
    //...

    public EmailReader() {
        // Initialize JMS
        jndi = new InitialContext();
        conFactory = (ConnectionFactory)
            jndi.lookup("jms/_defaultConnectionFactory");
        inbox = (Queue)jndi.lookup("jms/Inbox");
        connect = conFactory.createConnection("","");

        new Thread( 
            new Runnable() {
                public void run() {
                    if ( MODE == 1 )
                        listen11(); // JMS 1.1
                    else
                        listen20(); // JMS 2.0
                }
            } ).start();
    }

    // Create a consumer to read queue messages asynchronously 
    private void listen11() { // JMS 1.1
        try (Connection connect = 
                conFactory.createConnection("",""); ){
            connect.start();
            Session session = 
                connect.createSession(false,Session.AUTO_ACKNOWLEDGE);

            consumer = session.createConsumer(inbox);
            consumer.setMessageListener(this);

            // Do something while waiting for messages
        } 
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    private void listen20() { // JMS 2.0
        try (JMSContext context =
                conFactory.createContext(Session.AUTO_ACKNOWLEDGE);) {
            jmsconsumer = context.createConsumer(inbox);
            jmsconsumer.setMessageListener(EmailReader.this);

            // Do something while waiting for messages
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    public void onMessage(Message msg) 
    { ... }

    // ...
}
Listing 4 - The EmailReader class constructor differs slightly from the EmailSender class

First, using JNDI, the ConnectionFactory, Session, and Destination objects are created. Next, however, the code will call either the JMS 1.1 or JMS 2.0 code to setup the message listener in preparation to receive messages. The method listen11 uses the class API, updated to use try-with-resources, to create a JMS Connection and Consumer, and then set class as a message listener (since it implements the MessageListener interface.

The alternate method, listen20, uses the new JMS 2.0 simplified API. You’ll notice that there are fewer steps to setup the MessageListener callback. First, there’s no longer a need to create Connection and Session objects; these have been replaced with the new JMSContext class.

Once initialized, the application is free to continue with its own processing. In the case of an actual email client application, this may involve letting the user compose new email messages, organize existing email into folders, and so on. The provider will eventually call back on the application’s MessageListener object when a new message arrives. This is how JMS implements asynchronous messaging, allowing the client application to perform other tasks while waiting for messages to arrive.

JMS also supports synchronous messaging, which requires the client application (or one of its child threads) to block until a message arrives (see Listing 5). This type of an application is strictly an event-driven application, meaning it cannot do anything unless certain events occur; a JMS message arrival in this case.

public Email listenSync()  {
    try (JMSContext context = 
            conFactory.createContext(JMSContext.AUTO_ACKNOWLEDGE);) {
        JMSConsumer jmsconsumer = context.createConsumer(inbox);
        return jmsconsumer.receiveBody(Email.class);
    }
}    

private void waitForEmail() {
    //...
    while (true) { 
        displayEmail( listenSync() ); 
    }
}
Listing 5 - Synchronous Message delivery with JMS 2.0

This type of application can be developed to respond differently to different messages received, and can in effect be driven remotely by sending it the proper messages in the proper order. Synchronous JMS messaging will be explored more in depth later in this article, in the section on request/reply messaging.

Recall, from Listing 4 earlier, how the setMessageListener method was called with a reference to an object that implements the MessageListener interface. Because of this, once the JMS connection is started, the provider will asynchronously call the client application’s onMessage method when a message arrives for the destination the consumer is listening to. Listing 6 shows the EmailReader class implementations (one each for JMS 1.1 and 2.0) of onMessage.

public void onMessage(Message msg) {
    try {
        if ( MODE == 1 )
            onMessage11(msg);
        else
            onMessage20(msg);
    }
    catch ( Exception e ) {
        e.printStackTrace();
    }
}   

public void onMessage11(Message msg) //JMS 1.1
  throws JMSException {
    ObjectMessage objMsg = (ObjectMessage)msg;
    Email email = (Email)objMsg.getObject();
    displayEmail(email);
}

public void onMessage20(Message msg) //JMS 2.0
  throws JMSException {
    Email email = msg.getBody(Email.class);
    displayEmail(email);
}
Listing 6 - The EmailReader implementation of the MessageListener.onMessage method

In this code, a javax.jms.Message object is provided as a parameter to the onMessage method, representing the received JMS message. In the classic JMS 1.1 implementation, the message is cast to a javax.jms.ObjectMessage object, and the Email POJO is then extracted via a call to getObject. In contrast, in the simplified JMS 2.0 implementation, this is reduced to one step via the new getBody method on the Message object. For illustrative purposes, the remainder of the code simply prints out the email’s subject and body (see Figure 6).

Figure 6 - When the email reader application is run, the email messages that are in the “Inbox” queue are received and displayed

When this application is executed, it will receive all of the messages, one-by-one, that were placed on the queue earlier by the email sender application. Once all of the messages have been received, the application will simply wait to be notified that another message has been placed on the queue.

The new getBody method on Message works for more than object messages. You can use it to simplify the extraction of payloads of type String, or any of the supported JMS message types. However, it’s especially useful when BytesMessage types are used. For instance, the code in Listing 7 illustrates the extraction of a byte array from a JMS BytesMessage in the classic API.

BytesMessage bytesMsg = (BytesMessage)msg;
int length = (int)bytesMsg.getBodyLength();
byte[] myData = new byte[length];
int copied = bytesMsg.readBytes(myData);
// ...
Listing 7 - Extracting a BytesMessage payload in JMS 1.1

With the simplified JMS 2.0 API, this error-prone code has been reduced to this much simpler, single line of code:

byte[] myData = msg.getBody(byte[].class);

Publish-Subscribe (topic-based) Messaging

Topic-based messaging, also called publish-and-subscribe, implements a multicast-messaging paradigm, where one message is sent to potentially many consumers, as shown in Figure 7. A JMS client application can create a message consumer that registers interest in (or subscribes to) a topic. The consumer, and all other consumers listening on that same topic, will receive every message sent (or published to) that topic. Unless the subscription is made durable, messages published to a topic while a subscriber is not listening will be missed. This is in contrast to a queue where messages are stored until a receiver is active to receive them.

Figure 7 - Published messages are sent to zero or more subscribers

For the most part, each consumer should receive each message equally, at around the same time. Of course, simultaneous delivery cannot be guaranteed since outside factors such as network latencies, and application or server load will affect this. However, with all else being equal, all subscribers should be on a mostly-level playing field.

Message publishers and subscribers are also generally anonymous; they don’t need to know of each other’s existence. At any point in time, there may be zero or more publishers, or zero or more subscribers. Unless you build a notification system into your application, a message publisher won’t know if there are any subscribers listening on the topic at all, and vice-versa. This level of abstraction leads to a system of loosely coupled components, making the system more robust, and easier to maintain.

A topic is represented by the javax.jms.Topic interface, which extends the JMS Destination interface. Topics are administered objects, meaning they can be defined through the JMS provider’s administrative front-end, or they can be created by a client application on the fly at runtime.

Since topic-based messaging differs from queue-based messaging (one-to-many message receipt, and the potential to miss messages when not listening), they both tend to be used in very different scenarios. For instance, if you were building an order entry system, a queue would be the more logical choice for the messaging paradigm. If topic-based messaging were used in this case, order requests may be received by multiple listeners (and hence filled more than once), or be lost altogether if the application that processes the orders goes down.

Topic-based messaging is more suitable when you have multiple client applications that need to receive the same messages, where those messages are only important when the listener application is running. One example of this is a simple news headline ticker display. As messages containing news headlines are published, each running ticker application will receive and display those headlines. If a user is not running the ticker application at the time, it’s ok that the headlines are missed in that timeframe. Let’s implement this news ticker application now as an illustration of JMS topic-based messaging.

Sample News Headline Publisher Application

The news ticker demo application consists of a headline publisher application and a headline subscriber application. The publisher’s job is to interface with external news systems, gather the latest headlines, and publish them to the “NewsTicker” topic for all subscribers to receive. Since this is a sample application, the headlines are simply contained in an array of hard-coded Strings within the publisher application, but you get the point. The subscriber’s job is to listen for new headlines and display them as soon as they are received.

The code for the headline publisher application is in Listing 8. It’s very similar to the email sender application from the previous section, so the listing is abbreviated. However, there are differences that we’ll go over now.

package tickerdemo;
import javax.jms.*;
import javax.naming.*;

public class HeadlinePublisher {
    public static final String[] headlines = {
      "The Anatomy Of A Hack: A Network Of Defenses", 
      "How to involve the business in DevOps ", 
      "Cloud brokering: Enable IT and enable yourself",
      "Docker offers solutions for the top security risks",
      "What is Docker and what can it do for your dev team?",
      "IT infrastructure strategy and priorities for the CFO",
      "Embracing an Enterprise Agile Transformation"
    };
    
    private Connection connection = null;
    private Session session = null;
    private MessageProducer prod = null;

    public HeadlinePublisher() {
        try {
            // ...

            // Lookup the news ticker JMS topic
            Destination ticker = 
                (Destination)jndi.lookup("NewsTicker");

            // Create a producer to publish headline messages
            prod = session.createProducer(ticker);

            // ...
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    public void publishHeadline(String headline) throws Exception //...
    public static void main(String[] args) //...
}
Listing 8 - The partial contents of the HeadlinePublisher class

The first obvious difference has nothing to do with JMS, and that’s the list of headlines that this application will publish. Since it’s just a sample application, the headlines are hard-coded. The only true JMS-specific differences between this and the email (queue) sender application are with the destination name and type. The destination for this application is named “NewsTicker”, and since it’s a publish-and-subscribe topic it’s of type javax.jms.Topic.

As far as creating the JMS connection, session, and message producer, there are no differences with this topic publisher application from the email queue publisher application we explored previously. The JMS engineers purposely designed the interfaces to allow the code to be almost completely interchangeable, regardless of the destination type (queue or topic). This allows you to modify destination types administratively, at runtime, without requiring any code changes.

This is evident with the code in Listing 9 that contains the publishHeadline method and the code that calls it (the main method). Since a headline consists of only one line of text, a java.jms.TextMessage will suffice. This object is created via the Session object and initialized with the headline String passed as a parameter to the publishHeadline method. The message is sent via the call to send on the MessageProducer object.

private void publishJMS11() {
    try {
        Connection connection =
            connectionFactory.createConnection("","");

        // Create a JMS session object
        Session session = connection.createSession(
                                false, 
                                Session.AUTO_ACKNOWLEDGE);

        MessageProducer prod = session.createProducer(ticker);

        for (String headline: headlines ) {
            // Send the next headline
            TextMessage msg = session.createTextMessage(headline);
            prod.send(msg);

            // Simulate a natural pause between news headlines
            Thread.sleep(2000);
        }
    }
    catch ( Exception e ) {
        e.printStackTrace();
    }
}
Listing 9 - JMS 1.1 code within HeadlinePublisher to publish simulated headlines

The main method loops through the hard-coded headlines, pausing in between to make the simulation more realistic. As each headline is published, a debug message is displayed so you can tell that the application is doing something. Contrast this to the simplicity of the JMS 2.0 version in Listing 10.

private void publishJMS20() {
    try (JMSContext context = 
           connectionFactory.createContext();){
        for (String headline: headlines ) {
            context.createProducer().send(ticker, headline);
            Thread.sleep(2000);
        }
    } 
    catch (Exception ex) {
       ex.printStackTrace();
    }        
}
Listing 10 - JMS 2.0 code to publish simulated news headlines

Let’s look now at the news ticker application that receives and displays the headlines.

Sample News Headline Subscriber Application

To receive the news headlines published in the headline publisher sample application, we need to build a JMS MessageConsumer that listens for messages from the “NewsTicker” topic. Listing 11 contains a portion of the application’s main class, HeadlineSubscriber, which implements the MessageListener interface to receive JMS messages asynchronously.

package tickerdemo;
import javax.jms.*;
import javax.naming.*;

public class HeadlineSubscriber implements MessageListener {
    private Connection connection = null;
    private Session session = null;
    private MessageConsumer cons = null;

    public HeadlineSubscriber() {
        try {
            // ...
            
            // Create an asynchronous subscriber 
            cons = session.createConsumer(ticker);
            cons.setMessageListener(this);
            
            connection.start();
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }
    
    public void onMessage(Message msg)
    { ... }

    public static void main(String[] args)
    { ... }
}
Listing 11 - Subscribing to a topic to receive all published news headlines

Most of the code is similar to the other JMS applications we’ve already explored. As with the HeadlinePublisher class, this class gets a reference to the “NewsTicker” destination as a Topic object. Next, a MessageConsumer is created and a reference to the HeadlineSubscriber class is provided as the message listener. The class is now set to receive news headline messages asynchronously as they arrive.

Listing 12 contains the implementation of the onMessage method, which simply casts the received Message into a TextMessage object and displays the headline via the Message.getText method.

public void onMessage(Message msg) {
    try {
        TextMessage textMsg = (TextMessage)msg;
        System.out.println("New headline: " + textMsg.getText() );
    }
    catch ( Exception e ) {
        e.printStackTrace();
    }
}
Listing 12 - Messages are received asynchronously via the onMessage method

To test the news ticker, you need to start the HeadlineSubscriber application in one terminal window, and the HeadlinePublisher in a second terminal window, in that order. When you do this, you should see all of the news headlines displayed in order. Try the following experiments to see for yourself the differences between a queue and topic:

To further explore the differences between topics and queues, simply edit the news ticker sample application’s accompanying jndi.properties file, and change the destination type from a topic to a queue. You can do the same for the sample email application from the previous section. When you do, you should notice that email messages get lost if the reader application isn’t running when the messages are sent. Also, all email reader instances will receive all email messages. So much for privacy!

For the most part, JMS Queue and Topic processing are identical in terms of client application development. The differences lie in the semantics of message delivery, and that responsibility falls directly on the JMS provider, not the application programmer. It’s entirely possible to write generic JMS message processing code, and then determine which destinations should be queues, and which ones should be topics, administratively at runtime.

Let’s look at the third message paradigm that JMS supports, called request-and-reply messaging. Actually, this message paradigm is supported by, and implemented through, both the JMS queue and topic paradigms.

Request-and-Reply Messaging

As discussed earlier in this article, request-and-reply messaging is an exchange where a client makes a request to a server, and the server sends back a response. It’s a simple exchange, although this message paradigm does present some difficulties. For starters, both the client and server applications must be running simultaneously for the exchange to be successful. Next, scalability becomes an issue as one server may not be able to handle the load of a growing number of client requests. This may require the client to become aware of pools of servers, or some other load-balancing technology may need to be placed in between.

Finally, request-and-reply communication is synchronous by nature. This leads to a client that is serial in its behavior, which can limit its usefulness. For instance, a web browser is not of much use to the user during the time period after it makes its request to the server and before the page is sent back to be rendered. The delay between a user’s click on a link within a web page, and the time a new page appears on screen, can be a frustrating experience.

Nonetheless, request-and-reply messaging remains a simple, yet effective, means of communication that is supported in JMS. It’s useful as a means to notify a JMS sender component that its message was received and successfully processed by a JMS receiver component. The remoteness of the receiving application remains abstracted, as JMS request-and-reply messaging is simply another form of distributed application communication. It doesn’t matter if the sender and receiver components involved in this form of message exchange reside within the same running application, or are separated geographically (on different servers) and connected over the Internet.

Request-and-reply messaging can be achieved in JMS with publish-and-subscribe messaging by setting the JMSReplyTo property of the JMS message header. JMS even offers two utility classes, TopicRequestor and QueueRequestor, as façades for request-and-reply messaging in your code. For example, say we’ve been asked to implement read-receipt functionality in our email system. With this feature, when an email is sent, the sender gets a message back letting them know the email has been opened and read.

This is a feature that most modern email systems support, and it’s really a form or request-and-reply: the sender requests a reply from the receiver that the email was opened. Let’s look at the facilities that JMS provides to support request-and-reply.

Temporary Destinations

Every javax.jms.Message object contains a header and a body. The body holds the payload, or data, and this is the portion that is application-specific. In our example here, the payload is an email message represented by an Email object with two text fields: a subject and a body. The header fields are common to all JMS applications, although the client application has control over some of them. One example is the JMSReplyTo field. Before you send a message, you can set this field to a Destination (topic or queue) that you wish the recipient to send a reply message to. In the meantime, your code will need to listen on this Destination to receive the response message.

This means that a MessageProducer component that chooses request-and-reply messaging as its paradigm needs to be a MessageConsumer also. This can get confusing. To make it easier, JMS provides two types of requestor objects (which act as facades in that they wrap more complex code in an easy-to-use wrapper) to do most of the work of sending the request, and handling the reply, for you.

JMS Requestor Objects

As mentioned above, JMS provides two façade classes to help with request-and-reply messaging; javax.jms.QueueRequestor, and javax.jms.TopicRequestor. Both classes are used in similar fashion; the difference is that QueueRequestor uses a temporary Queue to handle the response, while TopicRequestor uses a Topic. Since the email sample application uses a queue for communication, we’ll concentrate on the QueueRequestor class for this example. Since both requestor classes look similar, this example applies to both.

Let’s modify the EmailSender class from the sample email application (which we examined earlier) to use a TopicRequestor to handle the email read receipt. The code in Listing 13 shows the modified sendEmail method.

public void sendEmail(String subject, String body) 
  throws Exception {
    Email email = new Email(subject, body);
    Message emailMsg = session.createObjectMessage(email);
    
    // Create the QuoteRequestor
    QueueRequestor req =
        new QueueRequestor( (QueueSession)session, 
                            (Queue)inbox);
    
    System.out.println("Send email w/subject: " + subject);

    // Send the message and get the reply synchronously
    Message reply = req.request(emailMsg);
 
    if ( reply != null ) 
        System.out.println("  read receipt received");
}
Listing 13 - The sendEmail method modified to use the QuoteRequestor object for request-reply

The email message POJO and the JMS ObjectMessage are both created as they were in the previous version of the sendEmail method. The first change is the addition of the QueueRequestor object. When it’s created, the email queue’s Session and Destination objects are passed as parameters to the constructor. Next, the call to send on the MessageProducer object is replaced by a call to request on the QueueRequestor object. This method will send the message to the queue, and will return the reply message when it’s sent back from the queue receiver. Although sending the message and handling the subsequent reply is accomplished via this one line of code, a lot more is done behind the scenes. The QueueRequestor object performs the following steps on your behalf:

Of course, this is only one half of the exchange: that of the message sender. Changes are also required within the message receiver code to check for, and respond to, the presence of a Destination object in the JMSReplyTo field. The code in Listing 14 contains an updated version of the EmailReader class implementation of onMessage.

public void onMessage(Message msg) {
    try {
        Email email = msg.getBody(Email.class);
        displayEmail(email);

        // Check to see if the sender wants a read-receipt
        Destination replyTo = objMsg.getJMSReplyTo();               
        if ( replyTo != null ) {
            // A MessageProducer is created for the reply destination
            MessageProducer responder =                             
                session.createProducer(replyTo);

            // Create the actual reply message
            Message reply = session.createMessage();

            // Send the reply message, indicating read-receipt
            responder.send(reply);
        }
    }
    catch ( Exception e ) {
        e.printStackTrace();
    }
}
Listing 14 - Check for a valid Destination object in each message’s JMSReplyTo field, and send a reply message in response

If a non-null value is found in the JMSReplyTo field, a reply must be sent. First, a MessageProducer is created for the reply Destination. Next, an empty reply Message is created, simply to indicate to the sender that the email was received. Finally, the reply message is sent to the reply Destination.

Although the QueueRequestor class saves you from writing some tedious code, it’s only useful in simple scenarios, such as this example. The class does have some limitations, such as the fact that it does not support more than one reply for each request–all other reply messages received on the temporary queue are ignored. There’s also no built-in support for persistent messaging, or transactions. Finally, the request method makes a blocking receive call; there is no way to specify a timeout value. Therefore, your code can potentially block forever if no reply is received.

For instance, if you run the modified EmailSender application while the EmailReader application is not running (a valid scenario), the sender will send the first email and block indefinitely waiting for a read-receipt reply message. It’s not until the email reader component is started that the remaining email message is finally sent. This is not the behavior we desire. We simply want the email message to be sent, with the JMSReplyTo field set as a way to indicate the desire for a read-receipt. The email sender component should then be free to perform other processing immediately (such as sending more email messages) even if the email message sits in the inbox queue for days.

However, the QueueRequestor class is provided as both a convenience, and as a model for building your own, enhanced, version of a requestor object. There’s no reason why you cannot modify the code to support these missing features, which is exactly what we’re going to do next.

Simplified Request-and-Reply

In order to simplify request-and-reply messaging, provide support for both Topic and Queue-based temporary destinations, and allow for asynchronous reply handling, we’re going to build our own requestor class, called GenericRequestor. This class will look and act similar to TopicRequestor and QueueRequestor, except it will allow the calling application to specify the use of a topic or a queue for the reply message. It will also be flexible enough to support both synchronous and asynchronous reply message handling, thus eliminating the associated problems encountered with the email application above.

The code in Listing 15 contains the constructors for the GenericRequestor class (the member variables and other methods have been omitted for now). The first three constructors simply call the fourth constructor--which does all of the work--providing default values for the missing parameters.

public class GenericRequestor {
    // ...
    
    public GenericRequestor(Session s, Destination d) {
        this(s, d, null, TemporaryTopic.class);
    }    

    public GenericRequestor( 
      Session s, Destination d, MessageListener l) {
        this(s, d, l, TemporaryTopic.class);
    }    

    public GenericRequestor(
      Session s, Destination d, Class reply) {
        this(s, d, null, reply);
    }    

    public GenericRequestor(Session session, 
                            Destination dest, 
                            MessageListener listener,
                            Class reply) 
      throws JMSException {
        this.session = session;
        this.requestDest = dest;
    
        // Create the correct reply destination 
        String replyStr = reply.getName();
        if ( replyStr.equals("javax.jms.TemporaryQueue"))
            replyDest = session.createTemporaryQueue();
        else
            replyDest = session.createTemporaryTopic();
    
        producer = session.createProducer(requestDest);
        consumer = session.createConsumer(replyDest);
        
        // Check for asynchronous request/reply
        if ( listener != null )
        {
            consumer.setMessageListener(listener);
            async = true; // flag set
        }
    }

    // ...
}
Listing 15 - The GenericRequestor class contains multiple constructors for flexibility

For instance, if the caller constructs the GenericRequestor class, but does not specify a reply Destination type, a TemporaryTopic object will be used by default. Additionally, synchronous request-and-reply messaging is assumed unless the constructor is used which accepts a MessageListener object. In that case, it’s assumed that the caller desires asynchronous request-and-reply messaging.

All of the interesting work is performed in the last constructor. First, the given Session and target Destination (to which the request is going) are stored. Next, either a TemporaryTopic or TemporaryQueue is created for the reply, based upon the parameters passed to the constructor. Again, the default is a TemporaryTopic. Next, a MessageProducer is created to handle sending the request message to the receiver, and a MessageConsumer is created to handle the response message from the receiver.

Finally, a check is made to see if a MessageListener object has been provided. If so, a flag is set for book-keeping, and the object is provided to the MessageConsumer via the setMessageListener call. All of this work is done on behalf of the calling application, and helps to save you from performing all of these steps each time request-and-reply messaging is desired.

Let’s take a look at the GenericRequestor.request method that an application calls to send a request message (see Listing 16). There are two versions of the request method, which we’ll examine in detail now.

public Message request(Message message) 
  throws JMSException {
    message.setJMSReplyTo(replyDest);
    producer.send(message);
    if ( ! async )
        return (consumer.receive());
    else
        return null;
}

public Message request(Message message, int timeout) 
  throws Exception {
    if ( async )
        throw new Exception( "invalid method call");
    
    message.setJMSReplyTo(replyDest);
    producer.send(message);
    return (consumer.receive(timeout));
}
Listing 16 - The two implementations of the request method

The first request method simply takes a Message object (the request message) to be delivered. This method sets the JMSReplyTo field to the reply destination that was created in the constructor. Next, the message is sent to the request Destination. Next, a check is made to see if the caller desires asynchronous messaging (indicated by a flag in the constructor) and if not, a call is made to the synchronous MessageConsumer.receive method. This call will block indefinitely until a response is received, which is then returned to the caller. This completes the synchronous request-and-reply sequence. If the async flag was set, the call to receive would not have been performed and the method would return immediately.

The second form of GenericRequestor.request takes an additional value to indicate how long the synchronous call to receive should wait for a reply before giving up. The caller can set this to a value that is reasonable for its processing. Since a timeout value only makes sense for synchronous messaging, an Exception will be thrown if the async flag was set.

The Updated Sample Email Application

To use this class with the sample email application, only minor changes need to be made to the EmailSender class (the EmailReader class remains unchanged). Listing 17 contains snippets from the EmailSender class to show only what has changed.

public class EmailSender implements MessageListener {
    // ...
    
    public EmailSender() { 
        // ...
    }

    public void sendEmail(String subject, String body) 
      throws Exception {
        Email email = new Email(subject, body);
        Message emailMsg = session.createObjectMessage(email);

        GenericRequestor req = 
            new GenericRequestor(
               (QueueSession)session, (Queue)inbox, this);
        
        System.out.println("Send email with subject: " + subject);

        Message reply = req.request(emailMsg);
        if ( reply != null )
            System.out.println("  read receipt received");
    }

    public void onMessage( Message msg ) {
        System.out.println("  read receipt received");
    }

    public void close() { 
        // ... 
    }

    public static void main(String[] args)  {
        // ...
        //demo.close();
    }
}
Listing 17 - EmailSender class updated to handle replies via the GenericRequestor class

The parts that have changed are in bold font. First, the class now implements the MessageListener interface, and in turn contains an implementation of the onMessage method. For demo purposes, this method simply displays a method that a read receipt message was received. In the full implementation, some sort of book-keeping would occur to correlate this message to the original request using the Message object’s CorrelationID header field.

Next, instead of using the TopicRequestor as in the original code, the new GenericRequestor object is used. The constructor chosen provides the Session, Destination, and the MessageListener objects. The reply destination type is omitted; hence it will use a TemporaryTopic by default. The caller can specify exactly which Destination type it wants to be used by passing either TemporaryTopic.class or TemporaryQueue.class before the MessageListener parameter.

Next, instead of using the TopicRequestor as in the original code, the new GenericRequestor object is used. The constructor chosen provides the Session, Destination, and the MessageListener objects. The reply destination type is omitted; hence it will use a TemporaryTopic by default. The caller can specify exactly which Destination type it wants to be used by passing either TemporaryTopic.class or TemporaryQueue.class before the MessageListener parameter.

When you run the sample email application now, it behaves as expected. If the email sender is run before the reader, it will send all email messages and patiently wait for the read receipt reply messages to arrive. When the email reader application starts some time later, it will receive the email messages off the queue, and will send back a read receipt message for each email. The email sender application, still waiting patiently, will display a message for each read receipt it receives. You can try running the applications now to see the results for yourself

Conclusion

Writing JMS client applications is straightforward once you understand the basics. This article has provided you with a sound understanding of JMS 1.1 and 2.0 publish-subscribe, point-to-point, and request-reply message paradigms, in the context of both asynchronous and synchronous messaging. The power of JMS is in the ability to leverage built-in transaction management, and reliable message delivery, without knowing much more than the basics.