Transaction and redelivery in JMS

Make the right transaction decision for your message delivery

Architecting and designing applications with the Java Message Service (JMS) requires not only knowing how to use the JMS API, but also having a solid foundation of its concepts. This article focuses on two such powerful concepts: transaction and redelivery. In JMS, a transaction organizes a message or message group into an atomic processing unit; failure to deliver a message may result in redelivery of that message or message group.

In this article, I help you develop a thorough understanding of your transaction options and show how you can evaluate their impact on message redelivery. I assume you have some familiarity with the JMS API as well as message-driven beans (MDBs).

Transaction option overview

An application has myriad transaction options available, including whether or not it wants to participate in transactions. If your application does not use transactions, it can use one of these acknowledgement modes: auto, duplicates okay, and client. You specify the acknowledgement modes when creating a JMS session. If your application uses transactions, it can choose from these transaction options: transacted session, MDB with container-managed transaction demarcation (CMTD), and MDB with bean-managed transaction demarcation (BMTD). The following lists briefly describe these acknowledgement modes and transaction options.

Acknowledgement options:

  • Auto mode: When a session uses auto mode, the messages sent or received from the session are automatically acknowledged. This is the simplest mode and expresses JMS's power by enabling once-only message delivery guarantee.

  • Duplicates okay mode: When a session uses duplicates okay mode, the messages sent or received from the session are automatically acknowledged just like auto mode, albeit lazily. Under rare circumstances, the messages might be delivered more than once. This mode enables at-least-once message delivery guarantee.

  • Client mode: When a session uses client mode, the messages sent or received from the session are not acknowledged automatically. The application must acknowledge the message receipt. This mode gives the application (rather than the JMS provider) complete control over message acknowledgement, at the cost of increased code complexity.

Other types of acknowledgement modes are possible. However, these acknowledgement modes are JMS provider specific, and therefore, compromise the JMS application portability.

Transaction options:

  • Transacted session: An application can participate in a transaction by creating a transacted session (or local transaction). The application completely controls the message delivery by either committing or rolling back the session.

  • Message-driven beans with CMTD: An MDB can participate in a container transaction by specifying CMTD in the XML deployment descriptor. The transaction commits upon successful message processing or the application can explicitly roll it back.

  • Message-driven beans with BMTD: An MDB can choose not to participate in a container transaction by specifying BMTD in the XML deployment descriptor. The MDB programmer has to design and code programmatic transactions.

Figure 1 depicts a decision tree of the previously mentioned transaction options.

Figure 1. Transaction options decision tree

Before studying the transaction options in detail, we'll explore the message delivery process.

Message delivery stages

Toward the end of delivery, the message conceptually passes through the following stages: message with JMS provider and message in application processing.

Message with JMS provider

In this stage, the message stays with the JMS provider just before the provider delivers it to the application. Consider a catastrophic situation where the JMS provider fails. What happens to the messages that the provider has not yet delivered to the client? Will the messages be lost?

The messages' fate depends not upon the transaction options outlined earlier, but rather upon the delivery mode. There are two delivery modes: nonpersistent and persistent. Messages with nonpersistent delivery modes are potentially lost if the JMS provider fails. Messages with persistent delivery modes are logged and stored to a stable storage. The JMS provider saves these messages to a stable storage, such as a database or a file system, and eventually delivers them to the application for processing.

Message in application processing

In this stage, the application receives the message from the JMS provider and processes it. Consider a failure occurring during message processing. What happens to the message? Will the message be lost or redelivered for successful processing later? The answers to these questions depend upon the transaction options you choose.

Figure 2 depicts the two processing stages. The diagram shows that a message moves from the JMS provider to application processing.

Figure 2. Message delivery stages

Throughout the remainder of the article, I use the action legend shown in Figure 3 to illustrate the different transaction options. As Figure 3 shows, a filled arrow depicts a JMS provider-performed action, whereas an outlined arrow depicts an application-performed action.

Figure 3. Action legend

The setup

To demonstrate the impact of various transaction options as well as redelivery, I will use one sender. The sender sends simple integers as object messages to a queue. Each transaction option has a different receiver. Each receiver demonstrates the impact of choosing a particular transaction option as well as highlights the impact on message redelivery. The sender and receivers utilize common administered objects: connection factory and queue. The connection factory is available using the Java Naming and Directory Interface (JNDI) name jms/QueueConnectionFactory, whereas the queue is available using the jms/Queue JNDI name.

Listing 1 shows the code for the sender:

Listing 1. Sender

package com.malani.examples.jms.transactions;
import javax.naming.InitialContext;
import javax.jms.*;
public class Sender {
    public static void main(String[] args) {
        System.out.println("Starting...");
        QueueConnectionFactory aQCF = null;
        QueueConnection aQC = null;
        QueueSession aQS = null;
        QueueSender aSender  = null;
        try {
            InitialContext aIC = new InitialContext(Resource.getResources());
            aQCF = (QueueConnectionFactory) aIC.lookup(
                iConstants.FACTORY_NAME
            );
            aQC = aQCF.createQueueConnection();
            aQS = aQC.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
            Queue aQueue = (Queue) aIC.lookup(iConstants.QUEUE_NAME);
            aSender = aQS.createSender(aQueue);
            aQC.start();
            for (int i = 0; i < 10; i++) {
                aSender.send(aQS.createObjectMessage(new Integer(i)));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (aSender != null) {
                    aSender.close();
                }
                if (aQS != null) {
                    aQS.close();
                }
                if (aQC != null) {
                    aQC.stop();
                    aQC.close();
                }
            } catch (JMSException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Ending...");
    }
}

The following sections describe each acknowledgement mode in detail. A receiver demonstrates each acknowledgement mode. Each case uses the sender above to demonstrate the impact and implications of implementing a specific transaction option.

Auto acknowledgement

To implement the auto acknowledgement mode, when you create the receiver's session, specify false as the first argument and Session.AUTO_ACKNOWLEDGE as the second argument of the createSession() factory method. Specifying false creates a nontransacted session. The second parameter creates a session that automatically acknowledges messages. A message is automatically acknowledged when it successfully returns from the receive() method. If the receiver uses the MessageListener interface, the message is automatically acknowledged when it successfully returns from the onMessage() method. If a failure occurs while executing the receive() method or the onMessage() method, the message is automatically redelivered. The JMS provider carefully manages message redelivery and guarantees once-only delivery semantics.

Listing 2 describes the Receiver class. The Receiver is the AutoReceiver class's superclass. The Receiver superclass does most of the heavy lifting. It receives the object messages sent by the Sender class. In the processMessage() method, the receiver prints the message:

Listing 2. Receiver

package com.malani.examples.jms.transactions;
import javax.jms.*;
import javax.naming.InitialContext;
import java.io.InputStreamReader;
public abstract class Receiver {
    protected void doAll() {
        QueueConnectionFactory aQCF = null;
        QueueConnection aQC = null;
        QueueSession aQS = null;
        QueueReceiver aQR  = null;
        try {
            InitialContext aIC = new InitialContext(Resource.getResources());
            aQCF = (QueueConnectionFactory) aIC.lookup(
                iConstants.FACTORY_NAME
            );
            aQC = aQCF.createQueueConnection();
            aQS = createQueueSession(aQC);
            final QueueSession aQS1 = aQS;
            Queue aQueue = (Queue) aIC.lookup(iConstants.QUEUE_NAME);
            aQR = aQS.createReceiver(aQueue);
            MessageListener aML = new MessageListener() {
                public void onMessage(Message aMessage) {
                    try {
                        processMessage(aMessage, aQS1);
                    } catch (JMSException e) {
                        e.printStackTrace();
                    }
                }
            };
            aQR.setMessageListener(aML);
            aQC.start();
            InputStreamReader aISR = new InputStreamReader(System.in);
            char aAnswer = ' ';
            do {
                aAnswer = (char) aISR.read();
                if ((aAnswer == 'r') || (aAnswer == 'R')) {
                    aQS.recover();
                }
            } while ((aAnswer != 'q') && (aAnswer != 'Q'));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (aQR != null) {
                    aQR.close();
                }
                if (aQS != null) {
                    aQS.close();
                }
                if (aQC != null) {
                    aQC.stop();
                    aQC.close();
                }
            } catch (JMSException e) {
                e.printStackTrace();
            }
        }
    }
    protected void processMessage(Message aMessage, QueueSession aQS) throws JMSException {
        if (aMessage instanceof ObjectMessage) {
            ObjectMessage aOM = (ObjectMessage) aMessage;
            System.out.print(aOM.getObject() + " ");
        }
    }
    protected abstract QueueSession createQueueSession(
        QueueConnection aQC
    ) throws JMSException;
}

Listing 3 describes the AutoReceiver class. As shown, the AutoReceiver creates a nontransacted session that automatically acknowledges messages in the createQueueSession() method:

Listing 3. AutoReceiver

package com.malani.examples.jms.transactions;
import javax.naming.InitialContext;
import javax.jms.*;
import java.io.InputStreamReader;
public class AutoReceiver extends Receiver {
    public static void main(String[] args) {
        System.out.println("Starting...");
        new AutoReceiver().doAll();
        System.out.println("Ending...");
    }
    protected QueueSession createQueueSession(
        QueueConnection aQC
        ) throws JMSException {
        return aQC.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
    }
}

Executing Listing 3 produces the following output; type character q and press Return to end the program:

Starting...
Java (TM) Message Service 1.0.2 Reference Implementation (build b14)
0 1 2 3 4 5 6 7 8 9 q
Ending...

In Figure 4, a message is automatically acknowledged after the application successfully processes it, which is after the message returns from the onMessage() method.

Figure 4. Auto acknowledgement

Duplicates okay acknowledgement

The duplicates okay acknowledgement mode closely resembles the auto acknowledgement mode. However, rather than pass Session.AUTO_ACKNOWLEDGE, you specify Session.DUPS_OK_ACKNOWLEDGE as the acknowledgement mode of createSession()'s second argument. With less overhead than auto mode, in duplicates okay mode, the JMS provider guarantees at-least-once message delivery. During failure recovery, certain messages are probably delivered more than once.

Listing 4 describes the DuplicatesOkayReceiver class, which extends the Receiver superclass. As shown, DuplicatesOkayReceiver creates a nontransacted session with duplicates okay acknowledgement mode in the createQueueSession() method:

Listing 4. DuplicatesOkayReceiver

1 2 3 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more