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.
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.
// 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.
// 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.
// 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) { ... }
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.
The messages consist of a header, properties, and a body. The header has a fixed structure with standard fields:
A unique message identifier generated by the middleware.
An optional message identifier of a related message.
The message destination.
An optional reply destination.
The message type, understood only by the sender and the recipient.
The message send time.
The message expiration time, computed from the send time and the message lifetime.
The earliest delivery time, computed from the send time and the minimum message delivery delay.
The message priority.
The delivery mode, either transient or persistent.
Indicates repeated delivery due to session recovery. The delivery count is reported in an associated property (JMS 2.0 and above).
Set Directly By Sender.
correlated message identifier
suggested reply destination
message type understood by recipient
Set Indirectly By Sender.
message recipient
message lifetime
message priority
PERSISTENT or NON_PERSISTENT
earliest message delivery time
Set Automatically By Middleware.
unique message identifier
message timestamp
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
.
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.
// 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.
// 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.
// Uses the classic API. TextMessage message; message = session.createTextMessage (); message.setText ("Hello"); // Always blocks until message is sent. sender.send (message);
// 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.
// Uses the classic API. TextMessage oMessage; oMessage = (TextMessage) recipient.receive (); oMessage = (TextMessage) recipient.receive (1000);
// Uses the classic API. public class SomeListener implements MessageListener { public void onMessage (Message message) { ... } } SomeListener oListener = new SomeListener (); recipient.setMessageListener (oListener);
// 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.
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.
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.
Java Message Service Specification. https://javaee.github.io/jms-spec
JSR 343: Java Message Service 2.0. https://jcp.org/aboutJava/communityprocess/final/jsr343/index.html