Java Platform, Enterprise Edition (Java EE) 8
The Java EE Tutorial

Previous Next Contents

The JMS API Programming Model

The basic building blocks of a JMS application are

  • Administered objects: connection factories and destinations

  • Connections

  • Sessions

  • JMSContext objects, which combine a connection and a session in one object

  • Message producers

  • Message consumers

  • Messages

Figure 48-5 shows how all these objects fit together in a JMS client application.

Figure 48-5 The JMS API Programming Model

Diagram of the JMS API programming model: connection factory, JMSContext, connection, session, message producer, message consumer, messages, and destinations

JMS also provides queue browsers, objects that allow an application to browse messages on a queue.

This section describes all these objects briefly and provides sample commands and code snippets that show how to create and use the objects. The last subsection briefly describes JMS API exception handling.

Examples that show how to combine all these objects in applications appear in Chapter 49, "Java Message Service Examples," beginning with Writing Simple JMS Applications. For more detail, see the JMS API documentation, part of the Java EE API documentation.

JMS Administered Objects

Two parts of a JMS application, destinations and connection factories, are commonly maintained administratively rather than programmatically. The technology underlying these objects is likely to be very different from one implementation of the JMS API to another. Therefore, the management of these objects belongs with other administrative tasks that vary from provider to provider.

JMS clients access administered objects through interfaces that are portable, so a client application can run with little or no change on more than one implementation of the JMS API. Ordinarily, an administrator configures administered objects in a JNDI namespace, and JMS clients then access them by using resource injection.

With GlassFish Server, you can use the asadmin create-jms-resource command or the Administration Console to create JMS administered objects in the form of connector resources. You can also specify the resources in a file named glassfish-resources.xml that you can bundle with an application.

NetBeans IDE provides a wizard that allows you to create JMS resources for GlassFish Server. See Creating JMS Administered Objects for details.

The Java EE platform specification allows a developer to create administered objects using annotations or deployment descriptor elements. Objects created in this way are specific to the application for which they are created. See Creating Resources for Java EE Applications for details. Definitions in a deployment descriptor override those specified by annotations.

JMS Connection Factories

A connection factory is the object a client uses to create a connection to a provider. A connection factory encapsulates a set of connection configuration parameters that has been defined by an administrator. Each connection factory is an instance of the ConnectionFactory, QueueConnectionFactory, or TopicConnectionFactory interface. To learn how to create connection factories, see Creating JMS Administered Objects.

At the beginning of a JMS client program, you usually inject a connection factory resource into a ConnectionFactory object. A Java EE server must provide a JMS connection factory with the logical JNDI name java:comp/DefaultJMSConnectionFactory. The actual JNDI name will be implementation-specific.

For example, the following code fragment looks up the default JMS connection factory and assigns it to a ConnectionFactory object:

@Resource(lookup = "java:comp/DefaultJMSConnectionFactory")
private static ConnectionFactory connectionFactory;

JMS Destinations

A destination is the object a client uses to specify the target of messages it produces and the source of messages it consumes. In the PTP messaging style, destinations are called queues. In the pub/sub messaging style, destinations are called topics. A JMS application can use multiple queues or topics (or both). To learn how to create destination resources, see Creating JMS Administered Objects.

To create a destination using GlassFish Server, you create a JMS destination resource that specifies a JNDI name for the destination.

In the GlassFish Server implementation of JMS, each destination resource refers to a physical destination. You can create a physical destination explicitly, but if you do not, the Application Server creates it when it is needed and deletes it when you delete the destination resource.

In addition to injecting a connection factory resource into a client program, you usually inject a destination resource. Unlike connection factories, destinations are specific to either the PTP or pub/sub messaging style. To create an application that allows you to use the same code for both topics and queues, you assign the destination to a Destination object.

The following code specifies two resources, a queue and a topic. The resource names are mapped to destination resources created in the JNDI namespace:

@Resource(lookup = "jms/MyQueue")
private static Queue queue;

@Resource(lookup = "jms/MyTopic")
private static Topic topic;

In a Java EE application, JMS administered objects are normally placed in the jms naming subcontext.

With the common interfaces, you can mix or match connection factories and destinations. That is, in addition to using the ConnectionFactory interface, you can inject a QueueConnectionFactory resource and use it with a Topic, and you can inject a TopicConnectionFactory resource and use it with a Queue. The behavior of the application will depend on the kind of destination you use and not on the kind of connection factory you use.

Connections

A connection encapsulates a virtual connection with a JMS provider. For example, a connection could represent an open TCP/IP socket between a client and a provider service daemon. You use a connection to create one or more sessions.

Note:

In the Java EE platform, the ability to create multiple sessions from a single connection is limited to application clients. In web and enterprise bean components, a connection can create no more than one session.

You normally create a connection by creating a JMSContext object. See JMSContext Objects for details.

Sessions

A session is a single-threaded context for producing and consuming messages.

You normally create a session (as well as a connection) by creating a JMSContext object. See JMSContext Objects for details. You use sessions to create message producers, message consumers, messages, queue browsers, and temporary destinations.

Sessions serialize the execution of message listeners; for details, see JMS Message Listeners.

A session provides a transactional context with which to group a set of sends and receives into an atomic unit of work. For details, see Using JMS Local Transactions.

JMSContext Objects

A JMSContext object combines a connection and a session in a single object. That is, it provides both an active connection to a JMS provider and a single-threaded context for sending and receiving messages.

You use the JMSContext to create the following objects:

You can create a JMSContext in a try-with-resources block.

To create a JMSContext, call the createContext method on the connection factory:

JMSContext context = connectionFactory.createContext();

When called with no arguments from an application client or a Java SE client, or from the Java EE web or EJB container when there is no active JTA transaction in progress, the createContext method creates a non-transacted session with an acknowledgment mode of JMSContext.AUTO_ACKNOWLEDGE. When called with no arguments from the web or EJB container when there is an active JTA transaction in progress, the createContext method creates a transacted session. For information about the way JMS transactions work in Java EE applications, see Using the JMS API in Java EE Applications.

From an application client or a Java SE client, you can also call the createContext method with the argument JMSContext.SESSION_TRANSACTED to create a transacted session:

JMSContext context =
        connectionFactory.createContext(JMSContext.SESSION_TRANSACTED);

The session uses local transactions; see Using JMS Local Transactions for details.

Alternatively, you can specify a non-default acknowledgment mode; see Controlling Message Acknowledgment for more information.

When you use a JMSContext, message delivery normally begins as soon as you create a consumer. See JMS Message Consumers for more information.

If you create a JMSContext in a try-with-resources block, you do not need to close it explicitly. It will be closed when the try block comes to an end. Make sure that your application completes all its JMS activity within the try-with-resources block. If you do not use a try-with-resources block, you must call the close method on the JMSContext to close the connection when the application has finished its work.

JMS Message Producers

A message producer is an object that is created by a JMSContext or a session and used for sending messages to a destination. A message producer created by a JMSContext implements the JMSProducer interface. You could create it this way:

try (JMSContext context = connectionFactory.createContext();) {
    JMSProducer producer = context.createProducer();
    ...

However, a JMSProducer is a lightweight object that does not consume significant resources. For this reason, you do not need to save the JMSProducer in a variable; you can create a new one each time you send a message. You send messages to a specific destination by using the send method. For example:

context.createProducer().send(dest, message);

You can create the message in a variable before sending it, as shown here, or you can create it within the send call. See JMS Messages for more information.

JMS Message Consumers

A message consumer is an object that is created by a JMSContext or a session and used for receiving messages sent to a destination. A message producer created by a JMSContext implements the JMSConsumer interface. The simplest way to create a message consumer is to use the JMSContext.createConsumer method:

try (JMSContext context = connectionFactory.createContext();) {
    JMSConsumer consumer = context.createConsumer(dest);
    ...

A message consumer allows a JMS client to register interest in a destination with a JMS provider. The JMS provider manages the delivery of messages from a destination to the registered consumers of the destination.

When you use a JMSContext to create a message consumer, message delivery begins as soon as you have created the consumer. You can disable this behavior by calling setAutoStart(false) when you create the JMSContext and then calling the start method explicitly to start message delivery. If you want to stop message delivery temporarily without closing the connection, you can call the stop method; to restart message delivery, call start.

You use the receive method to consume a message synchronously. You can use this method at any time after you create the consumer.

If you specify no arguments or an argument of 0, the method blocks indefinitely until a message arrives:

Message m = consumer.receive();
Message m = consumer.receive(0);

For a simple client, this may not matter. But if it is possible that a message might not be available, use a synchronous receive with a timeout: Call the receive method with a timeout argument greater than 0. One second is a recommended timeout value:

Message m = consumer.receive(1000); // time out after a second

To enable asynchronous message delivery from an application client or a Java SE client, you use a message listener, as described in the next section.

You can use the JMSContext.createDurableConsumer method to create a durable topic subscription. This method is valid only if you are using a topic. For details, see Creating Durable Subscriptions. For topics, you can also create shared consumers; see Creating Shared Subscriptions.

JMS Message Listeners

A message listener is an object that acts as an asynchronous event handler for messages. This object implements the MessageListener interface, which contains one method, onMessage. In the onMessage method, you define the actions to be taken when a message arrives.

From an application client or a Java SE client, you register the message listener with a specific message consumer by using the setMessageListener method. For example, if you define a class named Listener that implements the MessageListener interface, you can register the message listener as follows:

Listener myListener = new Listener();
consumer.setMessageListener(myListener);

When message delivery begins, the JMS provider automatically calls the message listener’s onMessage method whenever a message is delivered. The onMessage method takes one argument of type Message, which your implementation of the method can cast to another message subtype as needed (see Message Bodies).

In the Java EE web or EJB container, you use message-driven beans for asynchronous message delivery. A message-driven bean also implements the MessageListener interface and contains an onMessage method. For details, see Using Message-Driven Beans to Receive Messages Asynchronously.

Your onMessage method should handle all exceptions. Throwing a RuntimeException is considered a programming error.

For a simple example of the use of a message listener, see Using a Message Listener for Asynchronous Message Delivery. Chapter 49, "Java Message Service Examples," contains several more examples of message listeners and message-driven beans.

JMS Message Selectors

If your messaging application needs to filter the messages it receives, you can use a JMS message selector, which allows a message consumer for a destination to specify the messages that interest it. Message selectors assign the work of filtering messages to the JMS provider rather than to the application. For an example of an application that uses a message selector, see Sending Messages from a Session Bean to an MDB.

A message selector is a String that contains an expression. The syntax of the expression is based on a subset of the SQL92 conditional expression syntax. The message selector in the example selects any message that has a NewsType property that is set to the value 'Sports' or 'Opinion':

NewsType = 'Sports' OR NewsType = 'Opinion'

The createConsumer and createDurableConsumer methods, as well as the methods for creating shared consumers, allow you to specify a message selector as an argument when you create a message consumer.

The message consumer then receives only messages whose headers and properties match the selector. (See Message Headers and Message Properties.) A message selector cannot select messages on the basis of the content of the message body.

Consuming Messages from Topics

The semantics of consuming messages from topics are more complex than the semantics of consuming messages from queues.

An application consumes messages from a topic by creating a subscription on that topic and creating a consumer on that subscription. Subscriptions may be durable or nondurable, and they may be shared or unshared.

A subscription may be thought of as an entity within the JMS provider itself, whereas a consumer is a JMS object within the application.

A subscription will receive a copy of every message that is sent to the topic after the subscription is created, unless a message selector is specified. If a message selector is specified, only those messages whose properties match the message selector will be added to the subscription.

Unshared subscriptions are restricted to a single consumer. In this case, all the messages in the subscription are delivered to that consumer. Shared subscriptions allow multiple consumers. In this case, each message in the subscription is delivered to only one consumer. JMS does not define how messages are distributed between multiple consumers on the same subscription.

Subscriptions may be durable or nondurable.

A nondurable subscription exists only as long as there is an active consumer on the subscription. This means that any messages sent to the topic will be added to the subscription only while a consumer exists and is not closed.

A nondurable subscription may be either unshared or shared.

  • An unshared nondurable subscription does not have a name and may have only a single consumer object associated with it. It is created automatically when the consumer object is created. It is not persisted and is deleted automatically when the consumer object is closed.

    The JMSContext.createConsumer method creates a consumer on an unshared nondurable subscription if a topic is specified as the destination.

  • A shared nondurable subscription is identified by name and an optional client identifier, and may have several consumer objects consuming messages from it. It is created automatically when the first consumer object is created. It is not persisted and is deleted automatically when the last consumer object is closed. See Creating Shared Subscriptions for more information.

At the cost of higher overhead, a subscription may be durable. A durable subscription is persisted and continues to accumulate messages until explicitly deleted, even if there are no consumer objects consuming messages from it. See Creating Durable Subscriptions for details.

Creating Durable Subscriptions

To ensure that a pub/sub application receives all sent messages, use durable subscriptions for the consumers on the topic.

Like a nondurable subscription, a durable subscription may be either unshared or shared.

  • An unshared durable subscription is identified by name and client identifier (which must be set) and may have only a single consumer object associated with it.

  • A shared durable subscription is identified by name and an optional client identifier, and may have several consumer objects consuming messages from it.

A durable subscription that exists but that does not currently have a non-closed consumer object associated with it is described as being inactive.

You can use the JMSContext.createDurableConsumer method to create a consumer on an unshared durable subscription. An unshared durable subscription can have only one active consumer at a time.

A consumer identifies the durable subscription from which it consumes messages by specifying a unique identity that is retained by the JMS provider. Subsequent consumer objects that have the same identity resume the subscription in the state in which it was left by the preceding consumer. If a durable subscription has no active consumer, the JMS provider retains the subscription’s messages until they are received by the subscription or until they expire.

You establish the unique identity of an unshared durable subscription by setting the following:

  • A client ID for the connection

  • A topic and a subscription name for the subscription

You can set the client ID administratively for a client-specific connection factory using either the command line or the Administration Console. (In an application client or a Java SE client, you can instead call JMSContext.setClientID.)

After using this connection factory to create the JMSContext, you call the createDurableConsumer method with two arguments: the topic and a string that specifies the name of the subscription:

String subName = "MySub";
JMSConsumer consumer = context.createDurableConsumer(myTopic, subName);

The subscription becomes active after you create the consumer. Later, you might close the consumer:

consumer.close();

The JMS provider stores the messages sent to the topic, as it would store messages sent to a queue. If the program or another application calls createDurableConsumer using the same connection factory and its client ID, the same topic, and the same subscription name, then the subscription is reactivated and the JMS provider delivers the messages that were sent while the subscription was inactive.

To delete a durable subscription, first close the consumer, then call the unsubscribe method with the subscription name as the argument:

consumer.close();
context.unsubscribe(subName);

The unsubscribe method deletes the state the provider maintains for the subscription.

Figure 48-6 and Figure 48-7 show the difference between a nondurable and a durable subscription. With an ordinary, nondurable subscription, the consumer and the subscription begin and end at the same point and are, in effect, identical. When the consumer is closed, the subscription also ends. Here, create stands for a call to JMSContext.createConsumer with a Topic argument, and close stands for a call to JMSConsumer.close. Any messages sent to the topic between the time of the first close and the time of the second create are not added to either subscription. In Figure 48-6, the consumers receive messages M1, M2, M5, and M6, but they do not receive messages M3 and M4.

Figure 48-6 Nondurable Subscriptions and Consumers

Diagram showing messages being lost when nondurable subscriptions are used

With a durable subscription, the consumer can be closed and re-created, but the subscription continues to exist and to hold messages until the application calls the unsubscribe method. In Figure 48-7, create stands for a call to JMSContext.createDurableConsumer, close stands for a call to JMSConsumer.close, and unsubscribe stands for a call to JMSContext.unsubscribe. Messages sent after the first consumer is closed are received when the second consumer is created (on the same durable subscription), so even though messages M2, M4, and M5 arrive while there is no consumer, they are not lost.

Figure 48-7 Consumers on a Durable Subscription

Diagram showing messages being preserved when durable subscriptions are used

A shared durable subscription allows you to use multiple consumers to receive messages from a durable subscription. If you use a shared durable subscription, the connection factory you use does not need to have a client identifier. To create a shared durable subscription, call the JMSContext.createSharedDurableConsumer method, specifying the topic and subscription name:

JMSConsumer consumer =
        context.createSharedDurableConsumer(topic, "MakeItLast");

Creating Shared Subscriptions

A topic subscription created by the createConsumer or createDurableConsumer method can have only one consumer (although a topic can have many). Multiple clients consuming from the same topic have, by definition, multiple subscriptions to the topic, and all the clients receive all the messages sent to the topic (unless they filter them with message selectors).

It is, however, possible to create a nondurable shared subscription to a topic by using the createSharedConsumer method and specifying not only a destination but a subscription name:

consumer = context.createSharedConsumer(topicName, "SubName");

With a shared subscription, messages will be distributed among multiple clients that use the same topic and subscription name. Each message sent to the topic will be added to every subscription (subject to any message selectors), but each message added to a subscription will be delivered to only one of the consumers on that subscription, so it will be received by only one of the clients. A shared subscription can be useful if you want to share the message load among several consumers on the subscription rather than having just one consumer on the subscription receive each message. This feature can improve the scalability of Java EE application client applications and Java SE applications. (Message-driven beans share the work of processing messages from a topic among multiple threads.)

See Using Shared Nondurable Subscriptions for a simple example of using shared nondurable consumers.

You can also create shared durable subscriptions by using the JMSContext.createSharedDurableConsumer method. For details, see Creating Durable Subscriptions.

JMS Messages

The ultimate purpose of a JMS application is to produce and consume messages that can then be used by other software applications. JMS messages have a basic format that is simple but highly flexible, allowing you to create messages that match formats used by non-JMS applications on heterogeneous platforms.

A JMS message can have three parts: a header, properties, and a body. Only the header is required. The following sections describe these parts.

For complete documentation of message headers, properties, and bodies, see the documentation of the Message interface in the API documentation. For a list of possible message types, see Message Bodies.

The following topics are addressed here:

Message Headers

A JMS message header contains a number of predefined fields that contain values used by both clients and providers to identify and route messages. Table 48-1 lists and describes the JMS message header fields and indicates how their values are set. For example, every message has a unique identifier, which is represented in the header field JMSMessageID. The value of another header field, JMSDestination, represents the queue or the topic to which the message is sent. Other fields include a timestamp and a priority level.

Each header field has associated setter and getter methods, which are documented in the description of the Message interface. Some header fields are intended to be set by a client, but many are set automatically by the send method, which overrides any client-set values.

Table 48-1 How JMS Message Header Field Values Are Set

Header Field

Description

Set By

JMSDestination

Destination to which the message is being sent

JMS provider send method

JMSDeliveryMode

Delivery mode specified when the message was sent (see Specifying Message Persistence)

JMS provider send method

JMSDeliveryTime

The time the message was sent plus the delivery delay specified when the message was sent (see Specifying a Delivery Delay

JMS provider send method

JMSExpiration

Expiration time of the message (see Allowing Messages to Expire)

JMS provider send method

JMSPriority

The priority of the message (see Setting Message Priority Levels)

JMS provider send method

JMSMessageID

Value that uniquely identifies each message sent by a provider

JMS provider send method

JMSTimestamp

The time the message was handed off to a provider to be sent

JMS provider send method

JMSCorrelationID

Value that links one message to another; commonly the JMSMessageID value is used

Client application

JMSReplyTo

Destination where replies to the message should be sent

Client application

JMSType

Type identifier supplied by client application

Client application

JMSRedelivered

Whether the message is being redelivered

JMS provider prior to delivery

Message Properties

You can create and set properties for messages if you need values in addition to those provided by the header fields. You can use properties to provide compatibility with other messaging systems, or you can use them to create message selectors (see JMS Message Selectors). For an example of setting a property to be used as a message selector, see Sending Messages from a Session Bean to an MDB.

The JMS API provides some predefined property names that begin with JMSX. A JMS provider is required to implement only one of these, JMSXDeliveryCount (which specifies the number of times a message has been delivered); the rest are optional. The use of these predefined properties or of user-defined properties in applications is optional.

Message Bodies

The JMS API defines six different types of messages. Each message type corresponds to a different message body. These message types allow you to send and receive data in many different forms. Table 48-2 describes these message types.

Table 48-2 JMS Message Types

Message Type

Body Contains

TextMessage

A java.lang.String object (for example, the contents of an XML file).

MapMessage

A set of name-value pairs, with names as String objects and values as primitive types in the Java programming language. The entries can be accessed sequentially by enumerator or randomly by name. The order of the entries is undefined.

BytesMessage

A stream of uninterpreted bytes. This message type is for literally encoding a body to match an existing message format.

StreamMessage

A stream of primitive values in the Java programming language, filled and read sequentially.

ObjectMessage

A Serializable object in the Java programming language.

Message

Nothing. Composed of header fields and properties only. This message type is useful when a message body is not required.

The JMS API provides methods for creating messages of each type and for filling in their contents. For example, to create and send a TextMessage, you might use the following statements:

TextMessage message = context.createTextMessage();
message.setText(msg_text);     // msg_text is a String
context.createProducer().send(message);

At the consuming end, a message arrives as a generic Message object. You can then cast the object to the appropriate message type and use more specific methods to access the body and extract the message contents (and its headers and properties if needed). For example, you might use the stream-oriented read methods of BytesMessage. You must always cast to the appropriate message type to retrieve the body of a StreamMessage.

Instead of casting the message to a message type, you can call the getBody method on the Message, specifying the type of the message as an argument. For example, you can retrieve a TextMessage as a String. The following code fragment uses the getBody method:

Message m = consumer.receive();
if (m instanceof TextMessage) {
    String message = m.getBody(String.class);
    System.out.println("Reading message: " + message);
} else {
    // Handle error or process another message type
}

The JMS API provides shortcuts for creating and receiving a TextMessage, BytesMessage, MapMessage, or ObjectMessage. For example, you do not have to wrap a string in a TextMessage; instead, you can send and receive the string directly. For example, you can send a string as follows:

String message = "This is a message";
context.createProducer().send(dest, message);

You can receive the message by using the receiveBody method:

String message = receiver.receiveBody(String.class);

You can use the receiveBody method to receive any type of message except StreamMessage and Message, as long as the body of the message can be assigned to a particular type.

An empty Message can be useful if you want to send a message that is simply a signal to the application. Some of the examples in Chapter 49, "Java Message Service Examples," send an empty message after sending a series of text messages. For example:

context.createProducer().send(dest, context.createMessage());

The consumer code can then interpret a non-text message as a signal that all the messages sent have now been received.

The examples in Chapter 49, "Java Message Service Examples," use messages of type TextMessage, MapMessage, and Message.

JMS Queue Browsers

Messages sent to a queue remain in the queue until the message consumer for that queue consumes them. The JMS API provides a QueueBrowser object that allows you to browse the messages in the queue and display the header values for each message. To create a QueueBrowser object, use the JMSContext.createBrowser method. For example:

QueueBrowser browser = context.createBrowser(queue);

See Browsing Messages on a Queue for an example of using a QueueBrowser object.

The createBrowser method allows you to specify a message selector as a second argument when you create a QueueBrowser. For information on message selectors, see JMS Message Selectors.

The JMS API provides no mechanism for browsing a topic. Messages usually disappear from a topic as soon as they appear: If there are no message consumers to consume them, the JMS provider removes them. Although durable subscriptions allow messages to remain on a topic while the message consumer is not active, JMS does not define any facility for examining them.

JMS Exception Handling

The root class for all checked exceptions in the JMS API is JMSException. The root cause for all unchecked exceptions in the JMS API is JMSRuntimeException.

Catching JMSException and JMSRuntimeException provides a generic way of handling all exceptions related to the JMS API.

The JMSException and JMSRuntimeException classes include the following subclasses, described in the API documentation:

  • IllegalStateException, IllegalStateRuntimeException

  • InvalidClientIDException, InvalidClientIDRuntimeException

  • InvalidDestinationException, InvalidDestinationRuntimeException

  • InvalidSelectorException, InvalidSelectorRuntimeException

  • JMSSecurityException, JMSSecurityRuntimeException

  • MessageEOFException

  • MessageFormatException, MessageFormatRuntimeException

  • MessageNotReadableException

  • MessageNotWriteableException, MessageNotWriteableRuntimeException

  • ResourceAllocationException, ResourceAllocationRuntimeException

  • TransactionInProgressException, TransactionInProgressRuntimeException

  • TransactionRolledBackException, TransactionRolledBackRuntimeException

All the examples in the tutorial catch and handle JMSException or JMSRuntimeException when it is appropriate to do so.


Previous Next Contents
Oracle Logo  Copyright © 2017, Oracle and/or its affiliates. All rights reserved.