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
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.
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.
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. |
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. |
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.
 | 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:);
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:);
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:);
InitialContext ctx = new InitialContext(env);
TransactionManager transactionManager = (TransactionManager)
ctx.lookup("tx/TransactionManager");
transactionManager.begin();
Transaction transaction = transactionManager.getTransaction();
XAQueueConnectionFactory qconFactory = (XAQueueConnectionFactory)
ctx.lookup("jms/FooQueueConnectionFactory");
XAQueueConnection xaQueueConnection = qconFactory.createXAQueueConnection();
XAQueueSession xaQueueSession = xaQueueConnection.createXAQueueSession();
XAResource xaResource = xaQueueSession.getXAResource();
transaction.enlistResource(xaResource);
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. |