JavaTM Message Service Tutorial
Tutorial Homepage | TOC | Prev | Next | Index

5    Creating Robust JMS Applications

This chapter explains how to use features of the JMS API to achieve the level of reliability and performance your application requires. Many JMS applications cannot tolerate dropped or duplicate messages and require that every message be received once and only once.

The most reliable way to produce a message is to send a PERSISTENT message within a transaction. JMS messages are PERSISTENT by default. A transaction is a unit of work into which you can group a series of operations, such as message sends and receives, so that the operations either all succeed or all fail. For details, see Section 5.1.2, "Specifying Message Persistence," and Section 5.2.2, "Using JMS API Local Transactions."

The most reliable way to consume a message is to do so within a transaction, either from a nontemporary queue--in the PTP messaging domain--or from a durable subscription--in the pub/sub messaging domain. For details, see Section 5.1.5, "Creating Temporary Destinations"; Section 5.2.1, "Creating Durable Subscriptions"; and Section 5.2.2, "Using JMS API Local Transactions."

For other applications, a lower level of reliability can reduce overhead and improve performance. You can send messages with varying priority levels--see Section 5.1.3, "Setting Message Priority Levels"--and you can set them to expire after a certain length of time (see Section 5.1.4, "Allowing Messages to Expire").

The JMS API provides several ways to achieve various kinds and degrees of reliability. This chapter divides them into two categories:

The following sections describe these features as they apply to JMS clients. Some of the features work differently in J2EETM applications; in these cases, the differences are noted here and are explained in detail in Chapter 6.

5.1   Using Basic Reliability Mechanisms

The basic mechanisms for achieving or affecting reliable message delivery are as follows:

5.1.1   Controlling Message Acknowledgment

Until a JMS message has been acknowledged, it is not considered to be successfully consumed. The successful consumption of a message ordinarily takes place in three stages.

  1. The client receives the message.
  2. The client processes the message.
  3. The message is acknowledged. Acknowledgment is initiated either by the JMS provider or by the client, depending on the session acknowledgment mode.

In transacted sessions (see Section 5.2.2, "Using JMS API Local Transactions"), acknowledgment happens automatically when a transaction is committed. If a transaction is rolled back, all consumed messages are redelivered.

In nontransacted sessions, when and how a message is acknowledged depends on the value specified as the second argument of the createQueueSession or createTopicSession method. The three possible argument values are:

If messages have been received but not acknowledged when a QueueSession terminates, the JMS provider retains them and redelivers them when a consumer next accesses the queue. The provider also retains unacknowledged messages for a terminated TopicSession with a durable TopicSubscriber. (See Section 5.2.1, "Creating Durable Subscriptions.") Unacknowledged messages for a nondurable TopicSubscriber are dropped when the session is closed.

If you use a queue or a durable subscription, you can use the Session.recover method to stop a nontransacted session and restart it with its first unacknowledged message. In effect, the session's series of delivered messages is reset to the point after its last acknowledged message. The messages it now delivers may be different from those that were originally delivered, if messages have expired or higher-priority messages have arrived. For a nondurable TopicSubscriber, the provider may drop unacknowledged messages when its session is recovered.

The sample program in Section A.3, "Acknowledgment Modes," demonstrates two ways to ensure that a message will not be acknowledged until processing of the message is complete.

5.1.2   Specifying Message Persistence

The JMS API supports two delivery modes for messages to specify whether messages are lost if the JMS provider fails. These delivery modes are fields of the DeliveryMode interface.

You can specify the delivery mode in either of two ways.

If you do not specify a delivery mode, the default is PERSISTENT. Using the NON_PERSISTENT delivery mode may improve performance and reduce storage overhead, but you should use it only if your application can afford to miss messages.

5.1.3   Setting Message Priority Levels

You can use message priority levels to instruct the JMS provider to deliver urgent messages first. You can set the priority level in either of two ways.

The ten levels of priority range from 0 (lowest) to 9 (highest). If you do not specify a priority level, the default level is 4. A JMS provider tries to deliver higher-priority messages before lower-priority ones but does not have to deliver messages in exact order of priority.

5.1.4   Allowing Messages to Expire

By default, a message never expires. If a message will become obsolete after a certain period, however, you may want to set an expiration time. You can do this in either of two ways.

If the specified timeToLive value is 0, the message never expires.

When the message is published, the specified timeToLive is added to the current time to give the expiration time. Any message not delivered before the specified expiration time is destroyed. The destruction of obsolete messages conserves storage and computing resources.

5.1.5   Creating Temporary Destinations

Normally, you create JMS destinations--queues and topics--administratively rather than programmatically. Your JMS or J2EE provider includes a tool that you use to create and to remove destinations, and it is common for destinations to be long lasting.

The JMS API also enables you to create destinations--TemporaryQueue and TemporaryTopic objects--that last only for the duration of the connection in which they are created. You create these destinations dynamically, using the QueueSession.createTemporaryQueue and the TopicSession.createTemporaryTopic methods.

The only message consumers that can consume from a temporary destination are those created by the same connection that created the destination. Any message producer can send to the temporary destination. If you close the connection that a temporary destination belongs to, the destination is closed and its contents lost.

You can use temporary destinations to implement a simple request/reply mechanism. If you create a temporary destination and specify it as the value of the JMSReplyTo message header field when you send a message, the consumer of the message can use the value of the JMSReplyTo field as the destination to which it sends a reply and can also reference the original request by setting the JMSCorrelationID header field of the reply message to the value of the JMSMessageID header field of the request. For examples, see Chapter 9 and Chapter 10.

5.2   Using Advanced Reliability Mechanisms

The more advanced mechanisms for achieving reliable message delivery are the following:

5.2.1   Creating Durable Subscriptions

To make sure that a pub/sub application receives all published messages, use PERSISTENT delivery mode for the publishers. In addition, use durable subscriptions for the subscribers.

The TopicSession.createSubscriber method creates a nondurable subscriber. A nondurable subscriber can receive only messages that are published while it is active.

At the cost of higher overhead, you can use the TopicSession.createDurableSubscriber method to create a durable subscriber. A durable subscription can have only one active subscriber at a time.

A durable subscriber registers a durable subscription with a unique identity that is retained by the JMS provider. Subsequent subscriber objects with the same identity resume the subscription in the state in which it was left by the previous subscriber. If a durable subscription has no active subscriber, the JMS provider retains the subscription's messages until they are received by the subscription or until they expire.

You establish the unique identity of a durable subscriber by setting the following:

You set the client ID administratively for a client-specific connection factory using the j2eeadmin command. For example:

j2eeadmin -addJmsFactory MY_CON_FAC topic -props clientID=MyID

After using this connection factory to create the connection and the session, you call the createDurableSubscriber method with two arguments--the topic and a string that specifies the name of the subscription:

String subName = "MySub";
TopicSubscriber topicSubscriber = 
  topicSession.createDurableSubscriber(myTopic, subName);

The subscriber becomes active after you start the TopicConnection. Later on, you might close the TopicSubscriber:

topicSubscriber.close();

The JMS provider stores the messages published to the topic, as it would store messages sent to a queue. If the program or another application calls createDurableSubscriber with the same connection factory and its client ID, the same topic, and the same subscription name, the subscription is reactivated, and the JMS provider delivers the messages that were published while the subscriber was inactive.

To delete a durable subscription, first close the subscriber, and then use the unsubscribe method, with the subscription name as the argument:

topicSubscriber.close();
topicSession.unsubscribe("MySub");

The unsubscribe method deletes the state that the provider maintains for the subscriber.

Figure 5.1 and Figure 5.2 show the difference between a nondurable and a durable subscriber. With an ordinary, nondurable, subscriber, the subscriber and the subscription are coterminous and, in effect, identical. When a subscriber is closed, the subscription ends as well. Here, create stands for a call to TopicSession.createSubscriber, and close stands for a call to TopicSubscriber.close. Any messages published to the topic between the time of the first close and the time of the second create are not consumed by the subscriber. In Figure 5.1, the subscriber consumes messages M1, M2, M5, and M6, but messages M3 and M4 are lost.

Figure 5.1   Nondurable Subscribers and Subscriptions

With a durable subscriber, the subscriber can be closed and recreated, but the subscription continues to exist and to hold messages until the application calls the unsubscribe method. In Figure 5.2, create stands for a call to TopicSession.createDurableSubscriber, close stands for a call to TopicSubscriber.close, and unsubscribe stands for a call to TopicSession.unsubscribe. Messages published while the subscriber is closed are received when the subscriber is created again. So even though messages M2, M4, and M5 arrive while the subscriber is closed, they are not lost.

Figure 5.2   A Durable Subscriber and Subscription

See Chapter 8 for an example of a J2EE application that uses durable subscriptions. See Section A.1, "Durable Subscriptions," for an example of a client application that uses durable subscriptions.

5.2.2   Using JMS API Local Transactions

You can group a series of operations together into an atomic unit of work called a transaction. If any one of the operations fails, the transaction can be rolled back, and the operations can be attempted again from the beginning. If all the operations succeed, the transaction can be committed.

In a JMS client, you can use local transactions to group message sends and receives. The JMS API Session interface provides commit and rollback methods that you can use in a JMS client. A transaction commit means that all produced messages are sent and all consumed messages are acknowledged. A transaction rollback means that all produced messages are destroyed and all consumed messages are recovered and redelivered unless they have expired (see Section 5.1.4, "Allowing Messages to Expire").

A transacted session is always involved in a transaction. As soon as the commit or the rollback method is called, one transaction ends and another transaction begins. Closing a transacted session rolls back its transaction in progress, including any pending sends and receives.

In an Enterprise JavaBeansTM component, you cannot use the Session.commit and Session.rollback methods. Instead, you use distributed transactions, which are described in Chapter 6.

You can combine several sends and receives in a single JMS API local transaction. If you do so, you need to be careful about the order of the operations. You will have no problems if the transaction consists of all sends or all receives or if the receives come before the sends. But if you try to use a request-reply mechanism, whereby you send a message and then try to receive a reply to the sent message in the same transaction, the program will hang, because the send cannot take place until the transaction is committed. Because a message sent during a transaction is not actually sent until the transaction is committed, the transaction cannot contain any receives that depend on that message's having been sent.

It is also important to note that the production and the consumption of a message cannot both be part of the same transaction. The reason is that the transactions take place between the clients and the JMS provider, which intervenes between the production and the consumption of the message. Figure 5.3 illustrates this interaction.

Figure 5.3   Using JMS API Local Transactions

The sending of one or more messages to a queue by Client 1 can form a single transaction, because it forms a single set of interactions with the JMS provider. Similarly, the receiving of one or more messages from the queue by Client 2 also forms a single transaction. But because the two clients have no direct interaction, no transactions take place between them. Another way of putting this is that the act of producing and/or consuming messages in a session can be transactional, but the act of producing and consuming a specific message across different sessions cannot be transactional.

This is the fundamental difference between messaging and synchronized processing. Instead of tightly coupling the sending and receiving of data, message producers and consumers use an alternative approach to reliability, one that is built on a JMS provider's ability to supply a once-and-only-once message delivery guarantee.

When you create a session, you specify whether it is transacted. The first argument to the createQueueSession and the createTopicSession methods is a boolean value. A value of true means that the session is transacted; a value of false means that it is not transacted. The second argument to these methods is the acknowledgment mode, which is relevant only to nontransacted sessions (see Section 5.1.1, "Controlling Message Acknowledgment"). If the session is transacted, the second argument is ignored, so it is a good idea to specify 0 to make the meaning of your code clear. For example:

topicSession = topicConnection.createTopicSession(true, 0);

Because the commit and the rollback methods for local transactions are associated with the session, you cannot combine queue and topic operations in a single transaction. For example, you cannot receive a message from a queue and then publish a related message to a topic in the same transaction, because the QueueReceiver and the TopicPublisher are associated with a QueueSession and a TopicSession, respectively. You can, however, receive from one queue and send to another queue in the same transaction, assuming that you use the same QueueSession to create the QueueReceiver and the QueueSender. You can pass a client program's session to a message listener's constructor function and use it to create a message producer, so that you can use the same session for receives and sends in asynchronous message consumers. For an example of the use of JMS API local transactions, see Section A.2, "Transactions."



TOC | Prev | Next | Index

This Tutorial contains information on the 1.3.1 version of the Java 2 Platform, Enterprise Edition.

Copyright © 2002 Sun Microsystems, Inc. All rights reserved.