Dashboard > JJGuidelines > Home > Chapter 2 - Guidelines > 2. 6. JMS
JJGuidelines Log In | Sign Up   View a printable version of the current page.
2. 6. JMS
Added by Davy Vermeir, last edited by Stephan Janssen on Jan 11, 2007  (view change)

JMS

Introduction

A number of different Message-Oriented Middleware (MOM) products exist. Java Message Service (JMS) was introduced to abstract access to these products, in the same way JDBC abstracts access to relational databases and JNDI abstracts access to naming and directory services. A MOM product implementing the JMS API is called a JMS provider. Java applications can use the JMS API to access the JMS providers in a vendor neutral way, allowing them to run on more JMS providers without changing code. See rule JMS_001: Do Not Use A Vendor Specific Class.

JMS provides an asynchronous messaging system so that different systems can be integrated without being tightly coupled. One system can send messages to a second system, without requiring that the second system is running. The second system can process the messages when it is convenient. The sender is not required to wait for the message being received by the recipient.

It is also possible to use JMS in a synchronous way, for instance to implement a request/reply mechanism. Be sure to investigate carefully if JMS is the best choice in this case.

Overview Of JMS

A JMS application consists of one or more JMS servers and a number of JMS clients. A JMS client that produces messages is called a producer. A JMS client that consumes messages is called a consumer.

Structure Of A JMS Message

A JMS message contains:

  • a number of predefined headers
  • a number of custom properties
  • a body

The JMS API provides a number of messages, based on the body type of the message:

  • TextMessage
  • BytesMessage
  • ObjectMessage
  • StreamMessage
  • MapMessage
  • XMLMessage

Some providers define more messages. In order to have a portable application, you shouldn't use these proprietary messages. See rule JMS_001: Do Not Use A Vendor Specific Class. It is also important to choose the message type carefully to avoid unnecessary memory or processing overhead.

Tip

Watch out for persistent ObjectMessage's. Make sure that the class of the object in an ObjectMessage at consuming time is compatible with the class of the object at producing time. This could happen when a new version of an application is deployed while some ObjectMessage's are in the persistent store of the JMS server.

The predefined message headers are listed in the following table. A message provides get and set methods for each predefined header. The JMS client can set some of these headers, the vendor sets the other headers.

Table 2.10. Header Types
Header Set by Description
JMSMessageID provider The JMS provider attaches a unique message id to each produced message.
JMSDestination provider This header contains the destination of the message.
JMSDeliveryMode provider This header indicates if the message should be persistent or not.
JMSExpiration provider This header is a calculated based on the time to live specified in the message producer.It indicates when a message should be destroyed on the JMS server. Be careful with this header.
A JMS provider typically has a background thread to clean up all expired messages every once a while. Depending on how often this clean up is executed, messages that should have been destroyed, could still be alive. Executing the clean up with very short intervals will result in better results for the time to live aspect, but could have performance consequences.
JMSPriority provider This header indicates the priority a message should have for delivery to consumers. Values go from 0 to 9. Messages with a higher priority will be delivered before messages with a lower priority.
JMSCorrelationID application This optional header can be used to indicate that the current message is a reply to another message. The message id of the original message is put in the correlation id header of the reply.
JMSReplyTo application This optional header can be used to specify where a reply to this message can be put. The value can be the name of an administered destination or a temporary destination.
JMSRedelivered provider This header is set when a message is redelivered.
JMSTimestamp provider When a message is sent, the JMS provider puts a time stamp on the message.
JMSType application Some JMS providers use a message repository that contains the definitions of messages sent by applications. The JMSType header field may reference a message's definition in the provider's repository

Messaging Models

A distinction is made between two messaging models: point-to-point and publish-and-subscribe. A JMS Provider can implement one or both of these models:

  • Point-to-point (PTP)
  • Publish-and-subscribe (pub/sub)
Point-to-point

Point-to-point (PTP or p2p) is used for one-to-one messaging, with one sender (producer) who puts messages on a queue and one receiver (consumer) who consumes messages from a queue.

A message is delivered to only one receiver, even if more than one receiver is consuming messages from a specific queue. If no receivers are taking messages from a queue, the messages stay on the queue until a receiver becomes active.

Tip

It is possible to use more than one receiver to consume from a single queue. This could be a way of load balancing or concurrent processing of messages.

Note

The JMS provider only deletes a message from a queue after one consumer has received and acknowledged the message.

Publish-and-subscribe

Publish-and-subscribe (pub/sub) is used for one-to-many messaging or broadcasting, with one publisher (producer) who puts messages on a topic and any number of subscribers (consumers) who consume messages from a topic.

A message is copied and delivered to all subscribers of a topic. Only subscribers that are active get a copy of the message. A durable subscriber however can also consume messages that were produced when the subscriber was not active.

Note

The JMS provider only deletes a message from a topic after all active consumers and all durable consumers have received and acknowledged a copy of the message.

Administered Objects

JMS administered objects are created by a JMS administrator, not programmatically. JMS includes two types of administered objects:

  • Connection factories
  • Destinations
Tip

There is a convention of registering these administered objects in the JNDI namespace, so JMS clients can look them up in the JNDI namespace. See rule JEN_017: Use A Correct Name For A JMS Environment Reference Name (High). By using JNDI, JMS client applications remain portable across different JMS servers.

Connection Factories

For each messaging model, there exists a connection factory: QueueConnectionFactory and TopicConnectionFactory. A connection factory is used to create connections to the JMS server.

Destinations

In the context of PTP messaging, a destination is called a queue. In pub/sub, a destination is called a topic. These destination objects (Queue or Topic) support concurrent use. A JMS message is sent to a destination by a producer, and picked up from the destination by a consumer.

Temporary queues and topics are not administered objects. They can be created and deleted programmatically.

Note

There are certain restrictions in using temporary queues however, so they should only be used in certain circumstances. The main restriction is that only the JMS session that created the temporary destination is able to consume messages from it.

Connections

A JMS connection (Connection) represents a physical connection between the JMS client and the JMS server. JMS connections are rather heavyweight objects and allow concurrent use, so a JMS client application typically creates one connection for each messaging model it uses (QueueConnection and TopicConnection).

Note

It is not possible to do both PTP and pub/sub messaging over the same JMS connection. This will be possible in a future release of the JMS specification.

Tip

JMS provides an ExceptionListener interface to notify JMS clients of lost connections. It is the responsibility of the JMS provider to call the onException method of all registered ExceptionListener's after making reasonable attempts to reestablish the connection automatically. This is especially useful for a JMS client that only consumes messages using a MessageListener. In this case the JMS client won't detect the lost connection because it doesn't make any JMS calls itself.

Sessions

A JMS session (QueueSession and TopicSession) is created from a JMS connection and represents a client's conversational state with a JMS server. A session is considered a lightweight object and was designed for single threaded use only. It supports a single series of transactions and defines a serial order for the messages it consumes and the messages it produces. A session is also a message factory.

Note

JMS defines a serial order for messages only within a JMS session. Across different JMS sessions, this is no longer true. See rule JMS_010: Do Not Rely On JMS To Deliver Message In The Same Order As They Were Sent.

Delivery Mode

JMS messages can be persistent or non-persistent. Persistent messages are logged and stored to a stable storage. If messages are persistent, there is a guarantee that they will be delivered at least once.

Tip

Use persistent message if messages may never be lost in transit. Non-persistent messages can be lost if the JMS server crashes.

Note

Persistent messaging is slower than non-persistent messaging.

Message Sending

Sending messages is done with a QueueSender or a TopicPublisher, which both extend MessageProducer.

The send and publish methods are synchronous. These methods only return successfully if the JMS server acknowledged the message.

Tip

It is possible to send the same message object more than once, even to different destinations. The message producer will take a copy of the message before putting it on the destination.

Tip

A MessageProducer holds default values for priority, time to live and delivery mode. The MessageProducer will set the according headers on the messages while they are being sent.

Message Receiving

A client uses a MessageConsumer object to receive messages from a destination:

  • QueueReceiver for PTP
  • TopicSubscriber for pub/sub

A client may either synchronously receive a message consumer's messages or have the consumer asynchronously deliver them as they arrive.

For synchronous receipt, a client can request the next message from a message consumer using one of its receive methods that allow a client to poll or wait for the next message.

For asynchronous delivery, a client can register a MessageListener object with a message consumer. As messages arrive at the message consumer, it delivers them by calling the MessageListener's onMessage method.

Note

The JMS server only considers a message delivered after it has received an acknowledgement from the client. This is important for message redelivery.

Important

The choice between synchronous or asynchronous is very important. Synchronous message receiving should be preferred in a normal JMS client application. In web, session and entity bean components, only synchronous message receiving should be used. Message-driven beans were designed for asynchronous message receiving in a J2EE environment.

Message-driven Beans

Session beans, entity beans and web components can all act as JMS producers. However, they should only consume messages synchronously using the MessageConsumer's receive methods, because they are driven by synchronous request-reply protocols. See rule JMS_015: Do Not Receive Messages Asynchronously In A Web Component, A Session Bean Or An Entity Bean. Only the message-driven bean and application client components can both produce and consume asynchronous messages. Message-driven beans were introduced in EJB 2.0. A Message-driven bean implements the MessageListener interface, like a normal asynchronous message consumer, but the EJB container manages the its lifetime and possibly the transactions.

Note

A message-driven bean doesn't have a business interface, so it cannot be called synchronously. It can only be triggered by JMS messages.

Note

Message-driven beans are like stateless session beans; they don't maintain state between requests.

Tip

A big advantage of message-driven beans is that they can be used for concurrent processing. This is the case when the EJB container decides to select different bean instances to process different messages. This leads to higher throughput and better scalability in a robust server environment.

Note

Message-driven beans should not attempt to use the JMS API for message acknowledgment. Message acknowledgment is automatically handled by the container.
If the message-driven bean uses container managed transaction demarcation, message acknowledgment is handled automatically as a part of the transaction commit (the Required transaction attribute must be used).
If bean managed transaction demarcation is used, the message receipt cannot be part of the bean-managed transaction, and, in this case, the receipt is acknowledged by the container.

Selectors

Message selectors are used for message filtering. A consumer can be configured to receive only messages with properties matching the message selector expression.

Tip

It is important to evaluate the choice of putting messages on several destinations without using selectors, or putting messages on a single destination and dispatching them using selectors. The performance of using selectors should also be evaluated, especially if the destination holds a lot of messages.

Acknowledgment Modes For Message Receipt

The JMS provider considers a message to be consumed when it receives an acknowledgment for the message. A JMS session can be created with one of the following acknowledgment modes:

  • Session.AUTO_ACKNOWLEDGE
  • Session.CLIENT_ACKNOWLEDGE
  • Session.DUPS_OK_ACKNOWLEDGE

The acknowledgment mode is only taken into account in a non-transactional session. Choosing the acknowledgment mode has serious consequences for message redelivery.

AUTO_ACKNOWLEDGE

With this acknowledgment mode, the session automatically acknowledges a client's receipt of a message either when the session has successfully returned from a call to receive or when the message listener the session has called to process the message successfully returns.

Warning

If the receive or onMessage method throws an exception, the message will be redelivered automatically. It is also possible that the JMS provider fails to acknowledge the message. In these cases, the message will be redelivered (even if the message was successfully processed the first time), but the JMSRedelivered flag will be set. To guard against duplicate messages, the application should check this flag. A common solution to track duplicate messages is to keep message ids in a database table with a processed flag. This warning also holds for the DUPS_OK_ACKNOWLEDGE mode.

DUPS_OK_ACKNOWLEDGE

This acknowledgment mode instructs the session to lazily acknowledge the delivery of messages. This means that the JMS provider is not obliged to acknowledge each message. This could result in more messages being redelivered after a system failure.

Tip

Choose this mode if messages may be delivered more than once, but performance is more important than reliability. Under certain circumstance, such as a heavy load, there may be no performance gain.

CLIENT_ACKNOWLEDGE

With this acknowledgment mode, the client acknowledges a consumed message by calling the message's acknowledge method. This mode gives the JMS client application complete control over message acknowledgments. The acknowledge method informs the JMS provider that the message has been successfully received by the consumer. This method throws an exception in the case of a provider failure during the acknowledgment process. This results in redelivery of the message. The application should either undo the processing of this message or be prepared to ignore the redelivery of this message. The acknowledge method only has effect with this acknowledgment mode.

Tip

Calling the acknowledge method results in acknowledging all messages that were received in the current JMS session since the previous call to acknowledge (all previously unacknowledged messages are acknowledged). This gives the application the ability to group messages and consume either the whole group or no messages at all. All unacknowledged messages will be redelivered when a JMS session is restarted (for instance after a failure) or after calling the recover method on the session.

Transactions

Local transactions involve work performed on a single resource, like one JMS provider or one database. JMS also supports global transactions. These involve work performed across several different resources, for instance several databases and JMS providers.

Local Transactions

JMS sessions can be either transactional or non-transactional. When a session is transacted, all messages sent or received using that session, are automatically grouped in a transaction. The transaction remains open until either the rollback or the commit method is called. At this point a new transaction is started. If a transaction is rolled back or a failure occurs, all messages sent in the transaction are discarded. All received messages in the transaction will be redelivered with the redelivered flag set.

Transacted producers and consumers can be in a single transaction only if they were created from the same session object. This allows a JMS client to produce and consume messages in one single transaction. This can be useful if you want to send some messages as a result of receiving one or more messages.

Warning

It is important to realize that the production and the consumption of a JMS message can never be done in the same transaction. So waiting for a reply on a message that is sent in a transaction will always result in a deadlock. See rule JMS_009: Do Not Produce A Message In A Transaction And Rely On It To Be Consumed Before The End Of The Transaction. The replier will never see the message until the transaction is committed.

Note

For the moment, it is not possible to combine PTP and pub/sub messaging in a single transaction. This will be possible in future releases of the JMS specification.

Tip

Only use transactions if the functionality of acknowledgment modes is not sufficient.

Table 2.11. Local Transactions
Type Description
Non-transactional Sending Each sent message is automatically put on the destination.
Transactional Sending Sent messages are only put on the destination when the transaction commits. A transaction includes all messages sent since the last commit or rollback. A commit or a rollback automatically starts a new transaction. A rollback of a transaction results in no messages being put on the destination.
Non-transactional Receiving Message redelivery is done according to the acknowledgment mode.
Transactional Receiving Messages are acknowledged when the transaction commits. A rollback of a transaction automatically causes message redelivery.
Global Transactions

The two-phase commit protocol is used by a transaction manager to coordinate the interactions of resources in a global transaction. A resource can only participate in a global transaction if it supports the 2PC protocol, which is usually implemented using the XA interface developed by the Open Group. In the Java enterprise technologies, the XA interface is implemented by the Java Transaction API and XA interfaces (javax.transaction{{ and {{javax.transaction.xa packages). Any resource that implements these interfaces can be enrolled in a global transaction by a transaction manager that supports these interfaces.

The JMS specification provides XA versions of a number of JMS objects, like XAConnectionFactory, XAQueueConnection, XAQueueSession, ... Each of these objects works like its corresponding non-XA-compliant object. The transaction manager uses the XA interfaces directly, but a JMS client only sees the non-transactional versions.

Dead Message Queue

Some vendors have a notion of a Dead Message Queue. Messages are put on this queue when they are undeliverable. This could happen if they have expired, or if there is a deployment configuration problem. Without a Dead Message Queue, these messages would be lost. Messages from a Dead Message Queue can also be consumed, as from a normal queue. An application should provide some logic for handling dead messages, or at least make sure the Dead Message Queue doesn't fill up.

JMS Examples

Sending Messages From A JMS Client

There are a number of steps to be taken in order to send messages:

Lookup connection factory (JMS administered object)
Create connection: choose between PTP and pub/sub
Lookup destinations (JMS administered objects)
Create sessions (single threaded context!)
Create senders
Create message
Send message
Close resources

public void testSend() {
    QueueConnection qcon = null;
    QueueSession qsession = null;
    QueueSender qsender = null;
    try {
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "weblogic.jndi.WLInitialContextFactory");
        env.put(Context.PROVIDER_URL, "t3://localhost:7001");
        InitialContext ctx = new InitialContext(env);
        QueueConnectionFactory qconFactory = (QueueConnectionFactory)
                ctx.lookup("jms/FooQueueConnectionFactory"); TODO_link_1

        qcon = qconFactory.createQueueConnection(); TODO_link_2
        Queue queue = (Queue) ctx.lookup("jms/FooQueue"); TODO_link_3
        qsession = qcon.createQueueSession(false,
                Session.AUTO_ACKNOWLEDGE); TODO_link_4
        qsender = qsession.createSender(queue); TODO_link_5

        TextMessage msg = qsession.createTextMessage(); TODO_link_6
        msg.setText("some sample text");
        qsender.send(msg); TODO_link_7
    } catch (NamingException e) {
        // ...
    } catch (JMSException e) {
        // ...
    } finally {
        try {
            if (qsender != null) {
                qsender.close();
            }
            if (qsession != null) {
                qsession.close();
            }
            if (qcon != null) {
                qcon.close();
            }
            8
        } catch (JMSException e) {
            // ...
        }
    }
}

Receiving Messages From A JMS Client

There are a number of steps to be taken in order to receive messages. The following lines describe the steps to be taken, with a full example illustrating these steps.

Lookup connection factory (JMS administered object)
Create connection: choose between PTP and pub/sub
Lookup destinations (JMS administered objects)
Create sessions (single threaded context!)
Create receivers
Receive messages
Close resources

public void testReceive() {
    QueueConnection qcon = null;
    QueueSession qsession = null;
    QueueReceiver qreceiver = null;
    try {
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "weblogic.jndi.WLInitialContextFactory");
        env.put(Context.PROVIDER_URL, "t3://localhost:7001");
        InitialContext ctx = new InitialContext(env);
        QueueConnectionFactory qconFactory = (QueueConnectionFactory)
                ctx.lookup("jms/FooQueueConnectionFactory"); TODO_link_1

        qcon = qconFactory.createQueueConnection(); TODO_link_2
        Queue queue = (Queue) ctx.lookup("jms/FooQueue"); TODO_link_3
        qsession = qcon.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); TODO_link_4
        qreceiver = qsession.createReceiver(queue); TODO_link_5

        qreceiver.setMessageListener(new MessageListener() {
            public void onMessage(Message message) { TODO_link_6
                // ...
            }
        });
        qcon.start();
    } catch (NamingException e) {
        // ...
    } catch (JMSException e) {
        // ...
    } finally {
        try {
            if (qreceiver != null) {
                qreceiver.close();
            }
            if (qsession != null) {
                qsession.close();
            }
            if (qcon != null) {
                qcon.close();
            }
            7
        } catch (JMSException e) {
            // ...
        }
    }
}

JMS In a Global Transaction

The following example shows how JMS work can be explicitly enlisted in a global transaction.

public void testGlobalTransaction() {
    try {
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
        env.put(Context.PROVIDER_URL, "t3://localhost:7001");
        InitialContext ctx = new InitialContext(env);
        //lookup the transaction manager
        TransactionManager transactionManager = (TransactionManager)
                ctx.lookup("tx/TransactionManager");
        //start the global transaction
        transactionManager.begin();
        //get the transaction object that represents the global transaction
        Transaction transaction = transactionManager.getTransaction();

        XAQueueConnectionFactory qconFactory = (XAQueueConnectionFactory)
                ctx.lookup("jms/FooQueueConnectionFactory");
        //create an XA-compliant queue connection
        XAQueueConnection xaQueueConnection = qconFactory.createXAQueueConnection();
        //create an XA-compliant queue session
        XAQueueSession xaQueueSession = xaQueueConnection.createXAQueueSession();
        //get the XAResource
        XAResource xaResource = xaQueueSession.getXAResource();
        //enlist the resource in the current transaction
        transaction.enlistResource(xaResource);
        // do some JMS work

        // Enlist some other XAResources in the current transaction
        // and do some work with them

        //commit the transaction
        transaction.commit();
    } catch (NamingException e) {
        // ...
    } catch (NotSupportedException e) {
        // ...
    } catch (SystemException e) {
        // ...
    } catch (JMSException e) {
        // ...
    } catch (RollbackException e) {
        // ...
    } catch (HeuristicMixedException e) {
        // ...
    } catch (HeuristicRollbackException e) {
        // ...
    }
}
Tip

When you are using container-managed transactions, all this is done by the EJB container.

If using the Spring framework make sure you use the JMS abstraction framework which both supports JMS 1.0.2 and 1.1.

Site powered by a free Open Source Project / Non-profit License (more) of Confluence - the Enterprise wiki.
Learn more or evaluate Confluence for your organisation.
Hosted by JavaLobby
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.2.5 Build:#520 Jun 27, 2006) - Bug/feature request - Contact Administrators