2.10. Java Message Service (JMS)

JMS (Java Message Service) is a standard interface for enterprise messaging middleware. JMS is a part of the J2EE platform, integrated with a wide spectrum of technologies including EJB (Enterprise Java Beans) and JNDI (Java Naming and Directory Interface). The JMS standard exists in two major revisions, 1.x and 2.x, the text in this section deals separately with the two versions where necessary.

2.10.1. Architecture

The architecture of JMS assumes an existence of an enterprise messaging service provider, which needs to be connected to before it can be used. The act of connecting can be as simple as initializing a local library, or as complex as connecting to a remote enterprise messaging service provider. The details are hidden from the client, who simply creates a connection using a connection factory obtained from JNDI.

Connection Creation Example

// Get an initial naming context
Context initialContext = new InitialContext ();

// Look up the connection factory using
// a well known name in the initial context
ConnectionFactory connectionFactory;
connectionFactory = (ConnectionFactory) initialContext.lookup ("ConnectionFactory");

// Create a connection using the factory
Connection connection;
connection = ConnectionFactory.createConnection ();

// A connection only delivers messages
// once it is explicitly started
connection.start ();

All enterprise messaging communication takes place within the context of a session. The session context keeps track of things such as ordering, listeners and transactions. A session and its resources - producers and consumers but not destinations - are restricted for use by a single thread at any particular time. Multiple sessions can be used to allow multiple threads to communicate concurrently, however, there is no support for concurrent processing of messages delivered to a single consumer.

Session Creation Example

// Create a session for a connection, requesting
// no transaction support and automatic message
// acknowledgement
Session session;
session = connection.createSession (false, Session.AUTO_ACKNOWLEDGE);

The simplified API (JMS 2.0 and above) introduces context objects, which represent a single session in a single connection. The threading model restrictions still apply.

Context Creation Example

// Create a context that includes a connection and a session.
// Use try with resources to close the context when done.
try (JMSContext context = connectionFactory.createContext ()) {
    // Create another context reusing the same connection.
    try (JMSContext another = context.createContext ()) {
        ...
    } catch (JMSRuntimeException ex) { ... }
} catch (JMSRuntimeException ex) { ... }

2.10.2. Destinations

Destination objects are used to represent addresses. The standard assumes destinations will be created in the messaging service configuration and registered in JNDI. The Session interface provides methods for creating destinations, however, these are only meant to convert textual addresses into destination objects. The textual address syntax is not standardized.

The standard distinguishes two types of destinations. A queue is a destination for point-to-point communication. A message sent to a queue is stored until it is received and thus consumed by one recipient. A topic is a destination for publish-subscribe communication. A message sent to a topic is distributed to all currently connected recipients.

Temporary queues and temporary topics, with a scope limited to a single connection, are also available.

Destination Creation Example

Queue oQueue = oSession.createQueue ("SomeQueueName");
Topic oTopic = oSession.createTopic ("SomeTopicName");

Queue oTemporaryQueue = oSession.createTemporaryQueue ();
Topic oTemporaryTopic = oSession.createTemporaryTopic ();

2.10.3. Messages

The messages consist of a header, properties, and a body. The header has a fixed structure with standard fields:

JMSMessageID

A unique message identifier generated by the middleware.

JMSCorrelationID

An optional message identifier of a related message.

JMSDestination

The message destination.

JMSReplyTo

An optional reply destination.

JMSType

The message type, understood only by the sender and the recipient.

JMSTimestamp

The message send time.

JMSExpiration

The message expiration time, computed from the send time and the message lifetime.

JMSDeliveryTime

The earliest delivery time, computed from the send time and the minimum message delivery delay.

JMSPriority

The message priority.

JMSDeliveryMode

The delivery mode, either transient or persistent.

JMSRedelivered

Indicates repeated delivery due to session recovery. The delivery count is reported in an associated property (JMS 2.0 and above).

Message Header

Set Directly By Sender. 

JMSCorrelationID

correlated message identifier

JMSReplyTo

suggested reply destination

JMSType

message type understood by recipient

Set Indirectly By Sender. 

JMSDestination

message recipient

JMSExpiration

message lifetime

JMSPriority

message priority

JMSDeliveryMode

PERSISTENT or NON_PERSISTENT

JMSDeliveryTime

earliest message delivery time

Set Automatically By Middleware. 

JMSMessageID

unique message identifier

JMSTimestamp

message timestamp

JMSRedelivered

repeated delivery indication

Message properties are in fact optional message header fields. Properties are stored as name-value pairs with typed access interface. The standard reserves a unique name prefix for certain typical properties. These include for example user and application identity or current transaction context.

Messages can be filtered based on the value of message properties. The filters are specified using simple conditional expressions called message selectors.

The message body takes one of five shapes derived from the message type, namely BytesMessage, MapMessage, ObjectMessage, StreamMessage, TextMessage.

Message Body Types

StreamMessage

stream of primitive types

MapMessage

set of named values

TextMessage

java.lang.String

ObjectMessage

serializable object

BytesMessage

byte array

2.10.4. Producers and Consumers

The messages are sent by message producers and received by message consumers. The classic interfaces are MessageProducer and MessageConsumer, created by calling the appropriate session methods.

Producer And Consumer Creation Example

// Uses the classic API.

MessageProducer sender;
MessageConsumer recipient;

sender = session.createProducer (oQueue);
recipient = session.createConsumer (oQueue);

The simplified API interfaces to producers and consumers (JMS 2.0 and above) are JMSProducer and JMSConsumer, created by calling the appropriate context methods.

Producer And Consumer Creation Example

// Uses the simplified API.

// Configure sender with method chaining.
// Sender is not bound to destination here.
JMSProducer sender = context.createProducer ().
                     setDeliveryMode (PERSISTENT).
                     setDeliveryDelay (1000).
                     setTimeToLive (10000);

JMSConsumer recipient = context.createConsumer (oQueue);

The interfaces to send messages support various degrees of blocking, termed as synchronous and asynchronous (JMS 2.0 and above) message send. The standard does not define any interface that would guarantee non blocking operation.

Synchronous Message Send Example

// Uses the classic API.

TextMessage message;

message = session.createTextMessage ();
message.setText ("Hello");

// Always blocks until message is sent.
sender.send (message);

Synchronous Message Send Example

// Uses the simplified API.

// By default blocks until message is sent.
// Overloaded versions for all body types exist.
sender.send (oQueue, "Hello");

The interface to receive messages supports both blocking and nonblocking operation, termed as synchronous and asynchronous message receive in the standard.

The use of nonblocking communication is strongly related to the session threading model. As soon as a message listener is registered for a session of an active connection, that session becomes reserved for the internal thread implementing that listener, and neither the session nor the producers and consumers of the session can be called from other threads. It is safe to call the session or the associated objects from within the message listener using the listener thread. Registering a completion listener does not reserve the session, however, it is not safe to call the session from within the completion listener if it can be called from other code at the same time.

Message Receive Example

// Uses the classic API.

TextMessage oMessage;

oMessage = (TextMessage) recipient.receive ();
oMessage = (TextMessage) recipient.receive (1000);

Message Listener Example

// Uses the classic API.

public class SomeListener implements MessageListener {
    public void onMessage (Message message) {
        ...
    }
}

SomeListener oListener = new SomeListener ();
recipient.setMessageListener (oListener);

Message Receive Example

// Uses the simplified API.

// Template versions for all body types exist.
String body = consumer.receiveBody (String.class);

Message filters can be associated with message consumers.

Message Filter Example

String selector;
MessageConsumer receiver;

selector = new String ("(SomeProperty = 1000)");
receiver = session.createConsumer (oQueue, selector);

To guarantee reliable delivery, messages need to be acknowledged. Each session provides a recover method that causes unacknowledged messages to be delivered again. The acknowledgment itself can be done either automatically upon message delivery or manually by calling the acknowledge method on the message. When transactions are used, acknowledgment is done as a part of commit and recovery as a part of rollback.

A durable subscription to a topic can be requested. The messaging service stores messages for durable subscriptions of temporarily disconnected recipients.

Durable Subscriber Example

session.createDurableSubscriber (oTopic,"DurableSubscriberName");

A shared subscription to a topic can be requested (JMS 2.0 and above). The messaging service delivers messages for shared subscriptions to one of the connected recipients to provide load balancing.

Shared Subscriber Example

MessageConsumer consumer;

consumer = session.createSharedConsumer (oQueue, "SharedSubscriberName");

2.10.5. References

  1. Java Message Service Specification. https://javaee.github.io/jms-spec

  2. JSR 343: Java Message Service 2.0. https://jcp.org/aboutJava/communityprocess/final/jsr343/index.html