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

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

Executing Listing 4 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...

The difference between auto mode and duplicates okay mode is a classic tradeoff between delivery guarantee and throughput. With at-least-once message delivery guarantee, duplicates okay mode achieves higher throughput.

In Figure 5, a message is automatically acknowledged after the application successfully processes it, which is after the message successfully returns from the onMessage() method. The JMS provider does the same thing as in Figure 4, except in the duplicates okay mode, it acknowledges the message lazily.

Figure 5. Duplicates okay acknowledgement

Client acknowledgement

To implement client acknowledgement mode, when you create the receiver's session, specify false as createSession()'s first argument and Session.CLIENT_ACKNOWLEDGE as its second argument. Specifying false creates a nontransacted session. In client mode, invoking the Message class's acknowledge() method explicitly acknowledges the message. In fact, using the acknowledge() method makes sense when only using the client mode.

Listing 5 represents the ClientReceiver class. The ClientReceiver class extends the Receiver superclass. As shown, the ClientReceiver creates a nontransacted session where the client acknowledges the messages in the createQueueSession() method. In the processMessage() method, the ClientReceiver acknowledges only message 5:

Listing 5. ClientReceiver

package com.malani.examples.jms.transactions;
import javax.naming.InitialContext;
import javax.jms.*;
import java.io.InputStreamReader;
public class ClientReceiver extends Receiver {
    public static void main(String[] args) {
        System.out.println("Starting...");
        new ClientReceiver().doAll();
        System.out.println("Ending...");
    }
    protected QueueSession createQueueSession(
        QueueConnection aQC
        ) throws JMSException {
        return aQC.createQueueSession(false, Session.CLIENT_ACKNOWLEDGE);
    }
    protected void processMessage(Message aMessage, QueueSession aQS)
        throws JMSException
    {
        if (aMessage instanceof ObjectMessage) {
            ObjectMessage aOM = (ObjectMessage) aMessage;
            System.out.print(aOM.getObject() + " " );
            Integer i = (Integer) aOM.getObject();
            int ii = i.intValue();
            if (ii == 5) {
                aOM.acknowledge();
            }
        }
    }
}

Executing Listing 5 produces the following output:

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

Consider the following scenario: An application receives but does not acknowledge a message. The application receives a subsequent message and acknowledges it. What happens to the former message? The former message is also considered acknowledged. Generally, acknowledging a particular message acknowledges all prior messages the session receives. In the above output, only message 5 is explicitly acknowledged. All the messages before message 5 are implicitly acknowledged. Messages after message 5 are not acknowledged.

What happens to messages that are in the session but never acknowledged? The messages remain at the destination until they expire or forever if they lack an expiration date. Message redelivery is not automatic, but messages are redelivered under certain circumstances. First, calling the Session class's recover() method recovers the session. Invoking the recover() method causes the redelivery of all unacknowledged messages. Second, the receiving application restarts, causing the session to restart. Restarting the session causes all unacknowledged messages to be redelivered. In Listing 5's sample program, typing character r and pressing Return recovers the messages. Recovering the session causes the JMS provider to redeliver message 6, 7, 8, and 9.

In Figure 6, the application acknowledges the message by invoking the acknowledge() method when processing the message in the onMessage() method.

Figure 6. Client acknowledgement

Transacted session

To implement the transacted session mode, when creating the receiver's session, specify true as createSession()'s first argument. You ignore the createSession() method's second argument; to clearly denote its lack of use, pass in a dummy value such as -1.

The application indicates successful message processing by invoking the Session class's commit() method. The application can reject a message or a message group by invoking Session class's rollback() method. Calling the commit() method commits all the messages the session receives. Similarly, calling the rollback() method rejects all the messages the session receives. Session's commit() and rollback() methods make sense only with the transacted session option. The transacted session uses a chained-transaction model. In a chained-transaction model, an application does not explicitly start a transaction. Upon calling either the commit() or the rollback() methods, the application automatically starts a new transaction. Because a transaction is not explicitly started, it is always present and available.

Listing 6 describes the TransactedReceiver class. The TransactedReceiver class extends the Receiver superclass. As shown, the TransactedReceiver creates a transacted session in the createQueueSession() method. In the processMessage() method, the TransactedReceiver commits message 5 and rolls back message 9:

Listing 6. TransactedReceiver

package com.malani.examples.jms.transactions;
import javax.naming.InitialContext;
import javax.jms.*;
import java.io.InputStreamReader;
public class TransactedReceiver extends Receiver {
    public static void main(String[] args) {
        System.out.println("Starting...");
        new TransactedReceiver().doAll();
        System.out.println("Ending...");
    }
    protected QueueSession createQueueSession(
        QueueConnection aQC
        ) throws JMSException {
        return aQC.createQueueSession(true, -1);
    }
    protected void processMessage(Message aMessage, QueueSession aQS)
        throws JMSException
    {
        if (aMessage instanceof ObjectMessage) {
            ObjectMessage aOM = (ObjectMessage) aMessage;
            System.out.print(aOM.getObject() + " " );
            Integer i = (Integer) aOM.getObject();
            int ii = i.intValue();
            if (ii == 5) {
                aQS.commit();
            } else if (ii == 9) {
                aQS.rollback();
            }
        }
    }
}

Executing Listing 6 produces the following output:

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

In the above example, only message 5 is explicitly committed. All messages before message 5 are implicitly committed. Rolling back each message 9 causes all messages received after 5 to roll back.

What happens to rolled-back messages? The JMS provider automatically redelivers these messages so the application can reprocess them. In the above example, messages received after message 5 until message 9 (inclusive) are automatically redelivered. JMS providers may include these configuration parameters to fine-tune redelivery:

  • Redelivery count: The number of times to redeliver a message. Redelivery count is important because poison messages, messages the application can never successfully process, can eventually crash the system.
  • Exception destination: What happens to a message that is redelivered redelivery-count times? The JMS provider can do any of the following:
    • Log the message
    • Forward the message to an exception or error destination
    • Lose the message
  • Time to redeliver: An application that has just rolled back messages might not be ready to reprocess the same messages. This parameter specifies the time to wait before redelivering the message. This delay lets the JMS provider and the application recover to a stable state.

Does a redelivered message go to the back of the queue or sneak to the front? Barring message priorities and specifically sorted queues, the message sneaks to the queue's front. The default configuration sorts and delivers the messages by time. A redelivered message maintains its original timestamp. Consider this scenario: The sender sends messages 0, 1, 2, 3, 4, 5, 6, 7, 8, and 9. Now, the receiver processes 0, 1, 2, 3, and 4. Only message 5 is rolled back. Let's assume the receiver has processed messages 6 and 7 in the meantime. The receiver will process message 5 next (again) and continue with messages 8 and 9. Message 5 will not go to the end of the queue; thus, depending upon the configuration and how long it takes to process a message, message 5 will sneak to the front.

In Figure 7, the JMS provider automatically starts a transaction before it delivers a message to the application. The application commits or rolls back the transaction by invoking the commit() or rollback() methods. Since the transaction includes message delivery, a rollback causes the message to be redelivered.

Figure 7. Transacted session

Message-driven bean with CMTD

Upon deploying a message-driven bean, CMTD is specified in the XML deployment descriptor. The following XML fragment from the ejb-jar.xml depicts that the <transaction-type> attribute is Container:

    <message-driven>
      <ejb-name>cmtdBean</ejb-name>
      <ejb-class>com.malani.examples.jms.transactions.MDB_CMTD</ejb-class>
      <transaction-type>Container</transaction-type>
      <message-driven-destination>
        <destination-type>javax.jms.Queue</destination-type>
      </message-driven-destination>
    </message-driven>

Plus, the <trans-attribute> is specified as Required:

    <assembly-descriptor>
        <container-transaction>
            <method>
                <ejb-name>cmtdBean</ejb-name>
                <method-name>*</method-name>
            </method>
            <trans-attribute>Required</trans-attribute>
        </container-transaction>
    </assembly-descriptor>

A transaction automatically starts when the JMS provider removes the message from the destination and delivers it to the MDB's onMessage() method. The transaction commits upon successful return from the onMessage() method. Since the configuration is CMTD, the container manages the transaction's begin, commit, and roll back. An MDB can indicate to the container the transaction should be rolled back by calling MessageDrivenContext's setRollbackOnly() method. When a container rolls back a transaction, the message is automatically redelivered. This option's redelivery semantics and mechanics resemble that of the transacted session option.

Listing 7 describes the MDB_CMTD class, which implements the MessageListener interface's onMessage() method. In onMessage(), all messages above message 5 are rolled back by invoking MessageDrivenContext's setRollbackOnly() method:

Listing 7. MDB_CMTD

package com.malani.examples.jms.transactions;
import javax.ejb.*;
import javax.jms.*;
public class MDB_CMTD extends MDB {
    public void onMessage(Message aMessage) {
        try {
            if (aMessage instanceof ObjectMessage) {
                ObjectMessage aOM = (ObjectMessage) aMessage;
                System.out.print(aOM.getObject() + " " );
                Integer i = (Integer) aOM.getObject();
                int ii = i.intValue();
                if (ii > 5) {
                    mMDC.setRollbackOnly();
                }
                if (ii == 9) {
                    System.out.println();
                }
            }
        } catch (JMSException e) {
            e.printStackTrace();
        }
    }
}

Executing Listing 7 produces the following output:

0 1 2 3 4 5 6 7 8 9 
6 7 8 9 
6 7 8 9 
6 7 8 9 
6 7 8 9 
6 7 8 9 
6 7 8 9 
6 7 8 9

In the above output, any message greater than message 5 is rolled back using the setRollbackOnly() method. This causes the JMS provider to redeliver messages 6, 7, 8, and 9.

In Figure 8, the container automatically starts a transaction before the message is delivered to the application. The container automatically either commits or rolls back the transaction at the end of the onMessage() method. Invoking the setRollbackOnly() method indicates that the application rolls back the transaction. Since the transaction includes message delivery, a rollback causes the JMS provider to redeliver the message.

Figure 8. Message-driven bean with CMTD

Message-driven bean with BMTD

Upon deploying a message-driven bean, BMTD is specified in the XML deployment descriptor. The following XML fragment from the ejb-jar.xml depicts that the <transaction-type> attribute is Bean:

    <message-driven>
      <ejb-name>bmtdBean</ejb-name>
      <ejb-class>com.malani.examples.jms.transactions.MDB_BMTD</ejb-class>
      <transaction-type>Bean</transaction-type>
      <message-driven-destination>
        <destination-type>javax.jms.Queue</destination-type>
      </message-driven-destination>
    </message-driven>

A message-driven bean with BMTD can obtain a transaction by calling MessageDrivenContext's getUserTransaction() method. Invoking the UserTransaction interface's begin(), commit(), and rollback() methods controls the user transaction. Because the transaction starts inside the MDB, it does not span the JMS provider's message retrieval from the original destination. The transaction has no impact on message acknowledgement and in turn has no impact on message redelivery. Rolling back the transaction does not cause the messages to be redelivered.

To implement message-driven beans with BMTD, specify the Session.AUTO_ACKNOWLEDGE or Session.DUPS_OK_ACKNOWLEDGE as the acknowledgement mode. Hence, the MDB with BMTD option works with auto acknowledgement and duplications okay acknowledgement options.

Listing 8 describes the MDB_BMTD class, which implements the MessageListener interface's onMessage. A UserTransaction is obtained by invoking the MessageDrivenContext's getUserTransaction() method. The transaction is committed for messages below 5 (inclusive) and rolled back for messages above 5:

Listing 8. MDB_BMTD

package com.malani.examples.jms.transactions;
import javax.ejb.*;
import javax.jms.*;
import javax.transaction.UserTransaction;
public class MDB_BMTD extends MDB {
    public void onMessage(Message aMessage) {
        try {
            if (aMessage instanceof ObjectMessage) {
                UserTransaction aUT = mMDC.getUserTransaction();
                aUT.begin();
                ObjectMessage aOM = (ObjectMessage) aMessage;
                System.out.print(aOM.getObject() + " " );
                Integer i = (Integer) aOM.getObject();
                int ii = i.intValue();
                if (ii > 5) {
                    aUT.rollback();
                } else {
                    aUT.commit();
                }
                if (ii == 9) {
                    System.out.println();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Executing Listing 8 produces the following output:

0 1 2 3 4 5 6 7 8 9

In Figure 9, the application starts the transaction by invoking the UserTransaction's begin() method. The application either commits or rolls back the transaction by invoking either the UserTransaction's commit() or rollback() method. The container automatically acknowledges the message at the end of the onMessage() method. Since the transaction does not span the message retrieval from the original destination, rolling back the transaction does not result in message redelivery.

Figure 9. Message-driven bean with BMTD

Disclaimer

I tested the above examples with Java 2 SDK, Enterprise Edition 1.3. Your mileage may vary with different JMS providers and configuration options. For further information, please refer to the JMS provider's administration and configuration guide.

Knowledge is power

In this article, I presented and evaluated the redelivery impact in acknowledgement modes (auto, duplicates okay, and client) and transaction options (transacted session, MDB with CMTD, and MDB with BMTD). Understanding and applying transaction and redelivery concepts are crucial in leveraging and harnessing the JMS API's power and flexibility. This knowledge is critical in architecture, design, and implementation of JMS-based solutions.

I would sincerely like to thank Max Cooper, Stephen Ditlinger, Lina Chu, Roshni Malani, and Clare Zhang for reviewing this article.

Prakash Malani has extensive experience in designing and developing object-oriented software using Java and C++. He has developed software in many application domains, such as e-commerce, retail, medicine, communications, and interactive television. He practices and mentors in leading technologies, such as object-oriented analysis and design (OOAD), the Unified Modeling Language (UML), XML, Enterprise JavaBeans (EJBs), JavaServer Pages (JSPs), servlets, and Java DataBase Connectivity (JDBC). He teaches Java, OOA, OOD, UML, design patterns, and more at various institutions, including University of California, Irvine (UCI) and California State Polytechnic University, Pomona.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies