Java Messaging Service (JMS)

Java Messaging Service (JMS) is a Java API for interfacing with enterprise-level messaging software.  Traditional messaging software comes in two varieties: Publish-Subscribe (Pub/Sub) and Point-to-Point (PTP).  The Publish-Subscribe model uses a topic similar to a mailing list where different clients can publish to a topic or receive messages from a subscribed topic.  The Point-to-Point model uses a queue concept where clients send and receive messages.  A topic can have zero to many message consumers or subscribers, where as a queue can have at most one receiver, similar to a mailbox.

Messages

A message contains headers, optional properties, and a body.  All messages in the JMS API extend from the javax.jms.Message interface.  There are five different types of messages differentiated by the contents of its body:
 
Message Interface Body Content
javax.jms.BytesMessage array of bytes
javax.jms.MapMessage key-value pairs
javax.jms.ObjectMessage serialized Java object
javax.jms.StreamMessage stream of Java primitive values
javax.jms.TextMessage Java String object (used for XML)

The message headers are used to provide information for message processing.
 
Header Field Description
JMSCorrelationID links one message to another
JMSDeliveryMode javax.jms.DeliveryMode.NON_PERSISTENT or javax.jms.DeliveryMode.PERSISTENT
JMSDestination Topic or Queue this message is bound for
JMSExpiration expiration value (default is never)
JMSMessageID message ID
JMSPriority priority level (default is 4)
JMSRedelivered is message being redelivered
JMSReplyTo Topic or Queue where a reply should be sent
JMSTimestamp message timestamp
JMSType message type supplied by client

Properties added to the message are primarily used for message selection.  Using a subset of the SQL92 syntax, a message consumer can filter what messages are delivered based on a message selector.

Communication

All Destinations (Topics and Queues) are retrieved using a JNDI lookup.  The general steps are the same for interacting with either type of Destination:
  1. Obtain a javax.jms.ConnectionFactory using a JNDI lookup.  (The default ConnectionFactory names are different depending on the JMS provider.)
  2. Obtain a javax.jms.Connection from the ConnectionFactory.
  3. Obtain a javax.jms.Session from the Connection.
  4. Create a javax.jms.MessageProducer or javax.jms.MessageConsumer from the Session.
  5. For a MessageProducer, send the message, or for a MessageConsumer, receive the message (synchronous) or set the message listener (asynchronous).
  6. Start the Connection to start message delivery.
  7. Ultimately close the Connection.

Session

A Session is a single-threaded context for sending and receiving messages.  Messages are produced and/or consumed in serial; to increase salability, use an additional Session per thread.  If a program wants to both produce and consume messages, one Session must be used for each task.

Transactions

A Session can be optional transacted, where messages consumed and/or produced within a transaction form a single unit of work.  Upon a commit, all messages received are acknowledged and all produced messages are sent.  Alternately, if a transaction is rolledback, all sent messages are destroyed and all received messages are recovered.

Acknowledgment

There are three modes for non-transacted Sessions:
 
Acknowledgment Mode Description
DUPS_OK_ACKNOWLEDGE Session lazily acknowledges message delivery; minimizes Session overhead.
AUTO_ACKNOWLEDGE Session automatically acknowledges receipt of message after consumer finishes processing.
CLIENT_ACKNOWLEDGE Client calls Session.acknowledge() to acknowledge receipt of all messages received during the session.

Delivery Mode

NON_PERSISTENT messages do not need to be stored in persistent storage in case of client failure.  A JMS provider must deliver this kind of message at-most-once, i.e., the message can be lost, but can only be delivered once.

PERSISTENT messages are stored in persistent storage to be delivered at a later date if a client is unavailable.  A JMS provider must deliver this kind of message once-and-only once, i.e., it cannot be lost and cannot be delivered more than once.

Asynchronous Delivery

A JMS client that wants to receive messages asynchronously must implement the javax.jms.MessageListener interface, which only defines one method: public void onMessage(Message message).  The client must register itself with a MessageConsumer and the provider will call the onMessage() method as each message is delivered.

Message-Driven Beans (MDB)

A message-driven bean is anonymous asynchronous message consumer running within an EJB container.  An MDB has no home or remote interface, just the Bean class itself, and therefore has no client-visible identity.  A message-driven bean class must implement the javax.ejb.MessageDrivenBean interface.  As of the EJB 2.0 specification, an MDB can only consume JMS messages by implementing the javax.jms.MessageListener interface.  In the proposed EJB 2.1 specification, an MDB may also consume messages via the Java API for XML Messaging (JAXM) by implementing either the javax.xml.messaging.OneWayListener or javax.xml.messaging.ReqRespListener interfaces.

Clients can only interact with a message-driven bean through its associated JMS Destination, a non-durable/durable Topic or Queue.  The onMessage() method is invoked in the transaction scope specified in the deployment descriptor.  The only valid values are Required or NotSupported, because there can be no pre-existing transaction context or a client to handle exceptions.  The other methods defined for a MessageDrivenBean, newInstance(), setMessageDrivenContext(), ejbCreate(), and ejbRemove() are called within an unspecified transaction context.  Message-driven bean message acknowledgment is automatically handled by the container.  An acknowledgment mode is only needed for bean-managed transaction demarcation, and then the only values available are AUTO_ACKNOWLEDGE (the default) and DUPS_OK_ACKNOWLEDGE.

Deployment Descriptor

Message-driven beans are defined in the <message-driven> element, a new child of <enterprise-beans>.  Common child elements of <message-driven> are <ejb-class>, <transaction-type> (only Required or NotSupported), an optional <message-selector>, an optional <acknowledgment-mode> (only for bean-managed transactions), and <message-driven-destination> which defines <destination-type> (javax.jms.Topic or javax.jms.Queue) and optional <subscription-durability> (durable or nondurable on for Topics).

Example

A traditional Stock Ticker and Broker program.  The StockTickerServer starts up two StockTickerThread instances representing the NYSE and NASDAQ stock exchanges.  Each StockTickerThread sends a stock ticker message in XML format to the StockTicker topic.  The StockTickerMessageConsumer waits for asynchronous delivery from the StockTicker topic and sends the notification to the StockGUI client which updates the stock price.  When the user buys or sells a stock, the StockBroker sends a stock trade XML message to the StockTrader queue.  The message-driven bean StockTraderBean then asynchronously processes the StockTrader queue messages.

The application uses the Java API for XML Binding (JAXB) 1.0 Early Access release to bind the XML messages to generated Java classes.

Note: These directions are specific to JBoss, but can easily be adapted to other application servers.

Also, the application requires Jakarta Ant to build, deploy, and run.

Compilation and Execution

  1. Run ant with the jaxb target to generate the JAXB files:    ant jaxb
  2. Due to a bug in JAXB, the generated Stock.java file must have line 192 changed to: if (xs.atStart("shares")) {
  3. Copy jbossmq-destinations-service.xml and jaxb-rt-1.0-ea.jar to %JBOSS_HOME%\server\default\deploy
  4. Start JBoss
  5. Run ant with the default or deploy target:    ant
  6. Run ant with the server target in a new window:    ant server
  7. Run ant with the client target:    ant client