Jakarta Messaging Concepts

We are working on a fresh, updated Jakarta EE Tutorial. This section hasn’t yet been updated.

This chapter provides an introduction to Jakarta Messaging, a Java API that allows applications to create, send, receive, and read messages using reliable, asynchronous, loosely coupled communication.

Jakarta Messaging Overview

This overview defines the concept of messaging, describes Jakarta Messaging and where it can be used, and explains how Jakarta Messaging works within the Jakarta EE platform.

What Is Messaging?

Messaging is a method of communication between software components or applications. A messaging system is a peer-to-peer facility: A messaging client can send messages to, and receive messages from, any other client. Each client connects to a messaging agent that provides facilities for creating, sending, receiving, and reading messages.

Messaging enables distributed communication that is loosely coupled. A component sends a message to a destination, and the recipient can retrieve the message from the destination. What makes the communication loosely coupled is that the destination is all that the sender and receiver have in common. The sender and the receiver do not have to be available at the same time in order to communicate. In fact, the sender does not need to know anything about the receiver; nor does the receiver need to know anything about the sender. The sender and the receiver need to know only which message format and which destination to use. In this respect, messaging differs from tightly coupled technologies, such as Remote Method Invocation (RMI), which require an application to know a remote application’s methods.

Messaging also differs from electronic mail (email), which is a method of communication between people or between software applications and people. Messaging is used for communication between software applications or software components.

What Is Jakarta Messaging?

Jakarta Messaging is a Java API that allows applications to create, send, receive, and read messages. Jakarta Messaging defines a common set of interfaces and associated semantics that allow programs written in the Java programming language to communicate with other messaging implementations.

Jakarta Messaging minimizes the set of concepts a programmer must learn in order to use messaging products but provides enough features to support sophisticated messaging applications. It also strives to maximize the portability of Messaging applications across providers.

Jakarta Messaging enables communication that is not only loosely coupled but also

  • Asynchronous: A receiving client does not have to receive messages at the same time the sending client sends them. The sending client can send them and go on to other tasks; the receiving client can receive them much later.

  • Reliable: A messaging provider that implements Jakarta Messaging can ensure that a message is delivered once and only once. Lower levels of reliability are available for applications that can afford to miss messages or to receive duplicate messages.

The current version of the Jakarta Messaging specification is Version 3.0.

When Can You Use Jakarta Messaging?

An enterprise application provider is likely to choose a messaging API over a tightly coupled API, such as a remote procedure call (RPC), under the following circumstances.

  • The provider wants the components not to depend on information about other components' interfaces, so components can be easily replaced.

  • The provider wants the application to run whether or not all components are up and running simultaneously.

  • The application business model allows a component to send information to another and to continue to operate without receiving an immediate response.

For example, components of an enterprise application for an automobile manufacturer can use Jakarta Messaging in situations like the following.

  • The inventory component can send a message to the factory component when the inventory level for a product goes below a certain level so the factory can make more cars.

  • The factory component can send a message to the parts components so the factory can assemble the parts it needs.

  • The parts components in turn can send messages to their own inventory and order components to update their inventories and to order new parts from suppliers.

  • Both the factory and the parts components can send messages to the accounting component to update budget numbers.

  • The business can publish updated catalog items to its sales force.

Using messaging for these tasks allows the various components to interact with one another efficiently, without tying up network or other resources. Figure 1, “Messaging in an Enterprise Application” illustrates how this simple example might work.

Diagram showing messaging between various departments in an enterprise
Figure 1. Messaging in an Enterprise Application

Manufacturing is only one example of how an enterprise can use the Jakarta Messaging API. Retail applications, financial services applications, health services applications, and many others can make use of messaging.

How Does Jakarta Messaging Work with the Jakarta EE Platform?

When JMS was first introduced, its most important purpose was to allow Java applications to access existing messaging-oriented middleware (MOM) systems. Since that time, many vendors have adopted and implemented JMS, so a Jakarta Messaging product can now provide a complete messaging capability for an enterprise.

Jakarta Messaging is an integral part of the Jakarta EE platform, and application developers can use messaging with Jakarta EE components. Jakarta Messaging 2.0 is part of the Jakarta EE 8 release.

Jakarta Messaging in the Jakarta EE platform has the following features.

  • Application clients, Jakarta Enterprise Beans components, and web components can send or synchronously receive a Jakarta Messaging message. Application clients can in addition set a message listener that allows Jakarta Messaging messages to be delivered to it asynchronously by being notified when a message is available.

  • Message-driven beans, which are a kind of enterprise bean, enable the asynchronous consumption of messages in the enterprise bean container. An application server typically pools message-driven beans to implement concurrent processing of messages.

  • Message send and receive operations can participate in Jakarta transactions, which allow Jakarta Messaging operations and database accesses to take place within a single transaction.

Jakarta Messaging enhances the other parts of the Jakarta EE platform by simplifying enterprise development, allowing loosely coupled, reliable, asynchronous interactions among Jakarta EE components and legacy systems capable of messaging. A developer can easily add new behavior to a Jakarta EE application that has existing business events by adding a new message-driven bean to operate on specific business events. The Jakarta EE platform, moreover, enhances Jakarta Messaging by providing support for Jakarta Transactions and allowing for the concurrent consumption of messages. For more information, see the Jakarta Enterprise Beans specification, v4.0.

The Jakarta Messaging provider can be integrated with the application server using the Jakarta Connectors. You access the Messaging provider through a resource adapter. This capability allows vendors to create Messaging providers that can be plugged in to multiple application servers, and it allows application servers to support multiple Messaging providers. For more information, see the Jakarta Connectors specification, v2.0.

Basic Jakarta Messaging Concepts

This section introduces the most basic Jakarta Messaging concepts, the ones you must know to get started writing simple application clients that use the Jakarta Messaging.

The next section introduces the Jakarta Messaging programming model. Later sections cover more advanced concepts, including the ones you need in order to write applications that use message-driven beans.

Jakarta Messaging Architecture

A Jakarta Messaging application is composed of the following parts.

  • A Jakarta Messaging provider is a messaging system that implements the Messaging interfaces and provides administrative and control features. A Jakarta EE implementation that supports the Jakarta EE Platform also includes a Messaging provider.

  • Jakarta Messaging clients are the programs or components, written in the Java programming language, that produce and consume messages. Any Jakarta EE application component can act as a Messaging client.

    Java SE applications can also act as Jakarta Messaging clients; the Message Queue Developer’s Guide for Java Clients in the GlassFish Server documentation (https://glassfish.org/documentation) explains how to make this work.

  • Messages are the objects that communicate information between Jakarta Messaging clients.

  • Administered objects are Jakarta Messaging objects configured for the use of clients. The two kinds of Jakarta Messaging administered objects are destinations and connection factories, described in Jakarta Messaging Administered Objects. An administrator can create objects that are available to all applications that use a particular installation of GlassFish Server; alternatively, a developer can use annotations to create objects that are specific to a particular application.

Figure 2, “Jakarta Messaging Architecture” illustrates the way these parts interact. Administrative tools or annotations allow you to bind destinations and connection factories into a JNDI namespace. A Messaging client can then use resource injection to access the administered objects in the namespace and then establish a logical connection to the same objects through the Jakarta Messaging provider.

Diagram of Jakarta Messaging architecture, showing administrative tool, Jakarta Messaging client, JNDI namespace, and JMS provider
Figure 2. Jakarta Messaging Architecture

Messaging Styles

Before the Jakarta Messaging existed, most messaging products supported either the point-to-point or the publish/subscribe style of messaging. The Jakarta Messaging specification defines compliance for each style. A Messaging provider must implement both styles, and the Jakarta Messaging provides interfaces that are specific to each. The following subsections describe these messaging styles.

Jakarta Messaging, however, makes it unnecessary to use only one of the two styles. It allows you to use the same code to send and receive messages using either the PTP or the pub/sub style. The destinations you use remain specific to one style, and the behavior of the application will depend in part on whether you are using a queue or a topic. However, the code itself can be common to both styles, making your applications flexible and reusable. This tutorial describes and illustrates this coding approach, using the greatly simplified API provided by Jakarta Messaging 2.0.

Point-to-Point Messaging Style

A point-to-point (PTP) product or application is built on the concept of message queues, senders, and receivers. Each message is addressed to a specific queue, and receiving clients extract messages from the queues established to hold their messages. Queues retain all messages sent to them until the messages are consumed or expire.

PTP messaging, illustrated in Figure 3, “Point-to-Point Messaging”, has the following characteristics.

  • Each message has only one consumer.

  • The receiver can fetch the message whether or not it was running when the client sent the message.

Diagram of point-to-point messaging, showing Client 1 sending a message to a queue, and Client 2 consuming and acknowledging the message
Figure 3. Point-to-Point Messaging

Use PTP messaging when every message you send must be processed successfully by one consumer.

Publish/Subscribe Messaging Style

In a publish/subscribe (pub/sub) product or application, clients address messages to a topic, which functions somewhat like a bulletin board. Publishers and subscribers can dynamically publish or subscribe to the topic. The system takes care of distributing the messages arriving from a topic’s multiple publishers to its multiple subscribers. Topics retain messages only as long as it takes to distribute them to subscribers.

With pub/sub messaging, it is important to distinguish between the consumer that subscribes to a topic (the subscriber) and the subscription that is created. The consumer is a Jakarta Messaging object within an application, while the subscription is an entity within the Jakarta Messaging provider. Normally, a topic can have many consumers, but a subscription has only one subscriber. It is possible, however, to create shared subscriptions; see Creating Shared Subscriptions for details. See Consuming Messages from Topics for details on the semantics of pub/sub messaging.

Pub/sub messaging has the following characteristics.

  • Each message can have multiple consumers.

  • A client that subscribes to a topic can consume only messages sent after the client has created a subscription, and the consumer must continue to be active in order for it to consume messages.

    The Jakarta Messaging relaxes this requirement to some extent by allowing applications to create durable subscriptions, which receive messages sent while the consumers are not active. Durable subscriptions provide the flexibility and reliability of queues but still allow clients to send messages to many recipients. For more information about durable subscriptions, see Creating Durable Subscriptions.

Use pub/sub messaging when each message can be processed by any number of consumers (or none). Figure 4, “Publish/Subscribe Messaging” illustrates pub/sub messaging.

Diagram of pub/sub messaging, showing Client 1 sending a message to a topic, and the message being delivered to two consumers to the topic
Figure 4. Publish/Subscribe Messaging

Message Consumption

Messaging products are inherently asynchronous: There is no fundamental timing dependency between the production and the consumption of a message. However, the Jakarta Messaging specification uses this term in a more precise sense. Messages can be consumed in either of two ways.

  • Synchronously: A consumer explicitly fetches the message from the destination by calling the receive method. The receive method can block until a message arrives or can time out if a message does not arrive within a specified time limit.

  • Asynchronously: An application client or a Java SE client can register a message listener with a consumer. A message listener is similar to an event listener. Whenever a message arrives at the destination, the JMS provider delivers the message by calling the listener’s onMessage method, which acts on the contents of the message. In a Jakarta EE application, a message-driven bean serves as a message listener (it too has an onMessage method), but a client does not need to register it with a consumer.

Jakarta Messaging Programming Model

The basic building blocks of a Jakarta Messaging 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 5, “Jakarta Messaging Programming Model” shows how all these objects fit together in a Messaging client application.

Diagram of Jakarta Messaging programming model: connection factory, JMSContext, connection, session, message producer, message consumer, messages, and destinations
Figure 5. Jakarta Messaging Programming Model

Jakarta Messaging 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 Jakarta Messaging API exception handling.

Examples that show how to combine all these objects in applications appear in Jakarta Messaging Examples beginning with Writing Simple Jakarta Messaging Applications. For more detail, see Jakarta Messaging documentation, part of the Jakarta EE API documentation.

Jakarta Messaging Administered Objects

Two parts of a Jakarta Messaging 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 Jakarta Messaging to another. Therefore, the management of these objects belongs with other administrative tasks that vary from provider to provider.

Messaging 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 Jakarta Messaging. Ordinarily, an administrator configures administered objects in a JNDI namespace, and Messaging 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 Jakarta Messaging 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 Jakarta Messaging resources for GlassFish Server. See Creating Jakarta Messaging Administered Objects for details.

The Jakarta 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 Jakarta EE Applications for details. Definitions in a deployment descriptor override those specified by annotations.

Jakarta Messaging 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 Jakarta Messaging Administered Objects.

At the beginning of a Messaging client program, you usually inject a connection factory resource into a ConnectionFactory object. A Jakarta EE server must provide a Jakarta Messaging 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 Jakarta Messaging connection factory and assigns it to a ConnectionFactory object:

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

Jakarta Messaging 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 Jakarta Messaging application can use multiple queues or topics (or both). To learn how to create destination resources, see Creating Jakarta Messaging Administered Objects.

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

In the GlassFish Server implementation of Jakarta Messaging, 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 Jakarta EE application, Jakarta Messaging 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 Messaging 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.

In the Jakarta 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 Jakarta Messaging 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 Jakarta Messaging 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 Messaging 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 Jakarta EE web or Enterprise Beans container when there is no active Jakarta Transactions 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 Enterprise Beans container when there is an active JTA transaction in progress, the createContext method creates a transacted session. For information about the way Jakarta Messaging transactions work in Jakarta EE applications, see Using Jakarta Messaging in Jakarta 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 Jakarta Messaging 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 Jakarta Messaging 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 Jakarta Messaging 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.

Jakarta Messaging 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 Jakarta Messaging Messages for more information.

Jakarta Messaging 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 Messaging client to register interest in a destination with a Messaging provider. The Jakarta Messaging 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.

Jakarta Messaging 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 Messaging 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 Jakarta EE web or Enterprise Beans 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. Jakarta Messaging Examples contains several more examples of message listeners and message-driven beans.

Jakarta Messaging Message Selectors

If your messaging application needs to filter the messages it receives, you can use a Jakarta Messaging 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 Messaging 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 Messaging provider itself, whereas a consumer is a Jakarta Messaging 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. Jakarta Messaging 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 Messaging 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 Messaging 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 Messaging 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 Messaging 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 7, “Consumers on a Durable Subscription” 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 6, “Nondurable Subscriptions and Consumers”, the consumers receive messages M1, M2, M5, and M6, but they do not receive messages M3 and M4.

Diagram showing messages being lost when nondurable subscriptions are used
Figure 6. Nondurable Subscriptions and Consumers

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 7, “Consumers on a Durable Subscription”, 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.

Diagram showing messages being preserved when durable subscriptions are used
Figure 7. Consumers on a Durable Subscription

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");

See Acknowledging Messages, Using Durable Subscriptions, Using Shared Durable Subscriptions, and Sending Messages from a Session Bean to an MDB for examples of Jakarta EE applications that use durable subscriptions.

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 Jakarta 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.

Jakarta Messaging Messages

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

A Jakarta Messaging 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.

Message Headers

A Jakarta Messaging message header contains a number of predefined fields that contain values used by both clients and providers to identify and route messages. How Jakarta Messaging Message Header Field Values Are Set lists and describes the Jakarta Messaging 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.

How Jakarta Messaging 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)

Messaging 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)

Jakarta Messaging provider send method

JMSMessageID

Value that uniquely identifies each message sent by a provider

Messaging provider send method

JMSTimestamp

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

Messaging 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

Jakarta Messaging 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 Jakarta Messaging 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.

Jakarta Messaging provides some predefined property names that begin with JMSX. A Messaging 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

Jakarta Messaging 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. Jakarta Messaging Message Types describes these message types.

Jakarta Messaging 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.

Jakarta Messaging 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
}

Jakarta Messaging 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 Jakarta Messaging 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 Jakarta Messaging Examples, use messages of type TextMessage, MapMessage, and Message.

Jakarta Messaging Queue Browsers

Messages sent to a queue remain in the queue until the message consumer for that queue consumes them. Jakarta Messaging 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 Jakarta Messaging Message Selectors.

Jakarta Messaging 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 Messaging provider removes them. Although durable subscriptions allow messages to remain on a topic while the message consumer is not active, Jakarta Messaging does not define any facility for examining them.

Jakarta Messaging Exception Handling

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

Catching JMSException and JMSRuntimeException provides a generic way of handling all exceptions related to Jakarta Messaging.

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.

Using Advanced Jakarta Messaging Features

This section explains how to use features of Jakarta Messaging to achieve the level of reliability and performance your application requires. Many people use Jakarta Messaging in their applications because they cannot tolerate dropped or duplicate messages and because they require that every message be received once and only once. Jakarta Messaging provides this functionality.

The most reliable way to produce a message is to send a PERSISTENT message, and to do so within a transaction.

Jakarta Messaging messages are PERSISTENT by default; PERSISTENT messages will not be lost in the event of Messaging provider failure. For details, see Specifying Message Persistence.

Transactions allow multiple messages to be sent or received in an atomic operation.In the Jakarta EE platform they also allow message sends and receives to be combined with database reads and writes in an atomic transaction. A transaction is a unit of work into which you can group a series of operations, such as message sends and receives, so that the operations either all succeed or all fail. For details, see Using Jakarta Messaging Local Transactions.

The most reliable way to consume a message is to do so within a transaction, either from a queue or from a durable subscription to a topic. For details, see Creating Durable Subscriptions, Creating Temporary Destinations, and Using Jakarta Messaging Local Transactions.

Some features primarily allow an application to improve performance. For example, you can set messages to expire after a certain length of time (see Allowing Messages to Expire), so that consumers do not receive unnecessary outdated information. You can send messages asynchronously; see Sending Messages Asynchronously.

You can also specify various levels of control over message acknowledgment; see Controlling Message Acknowledgment.

Other features can provide useful capabilities unrelated to reliability. For example, you can create temporary destinations that last only for the duration of the connection in which they are created. See Creating Temporary Destinations for details.

The following sections describe these features as they apply to application clients or Java SE clients. Some of the features work differently in the Jakarta EE web or enterprise bean container; in these cases, the differences are noted here and are explained in detail in Using Jakarta Messaging in Jakarta EE Applications.

Controlling Message Acknowledgment

Until a Jakarta Messaging message has been acknowledged, it is not considered to be successfully consumed. The successful consumption of a message ordinarily takes place in three stages.

  1. The client receives the message.

  2. The client processes the message.

  3. The message is acknowledged.
    Acknowledgment is initiated either by the Messaging provider or by the client, depending on the session acknowledgment mode.

In locally transacted sessions (see Using Jakarta Messaging Local Transactions), a message is acknowledged when the session is committed. If a transaction is rolled back, all consumed messages are redelivered.

In a Jakarta transaction (in the Jakarta EE web or enterprise bean container) a message is acknowledged when the transaction is committed.

In nontransacted sessions, when and how a message is acknowledged depend on a value that may be specified as an argument of the createContext method. The possible argument values are as follows.

  • JMSContext.AUTO_ACKNOWLEDGE: This setting is the default for application clients and Java SE clients. The JMSContext automatically acknowledges a client’s receipt of a message either when the client has successfully returned from a call to receive or when the MessageListener it has called to process the message returns successfully.

    A synchronous receive in a JMSContext that is configured to use auto-acknowledgment is the one exception to the rule that message consumption is a three-stage process as described earlier. In this case, the receipt and acknowledgment take place in one step, followed by the processing of the message.

  • JMSContext.CLIENT_ACKNOWLEDGE: A client acknowledges a message by calling the message’s acknowledge method. In this mode, acknowledgment takes place on the session level: Acknowledging a consumed message automatically acknowledges the receipt of all messages that have been consumed by its session. For example, if a message consumer consumes ten messages and then acknowledges the fifth message delivered, all ten messages are acknowledged.

    In the Jakarta EE platform, the JMSContext.CLIENT_ACKNOWLEDGE setting can be used only in an application client, not in a web component or enterprise bean.
  • JMSContext.DUPS_OK_ACKNOWLEDGE: This option instructs the JMSContext to lazily acknowledge the delivery of messages. This is likely to result in the delivery of some duplicate messages if the Messaging provider fails, so it should be used only by consumers that can tolerate duplicate messages. (If the Messaging provider redelivers a message, it must set the value of the JMSRedelivered message header to true.) This option can reduce session overhead by minimizing the work the session does to prevent duplicates.

If messages have been received from a queue but not acknowledged when a JMSContext is closed, the Messaging provider retains them and redelivers them when a consumer next accesses the queue. The provider also retains unacknowledged messages if an application closes a JMSContext that has been consuming messages from a durable subscription. (See Creating Durable Subscriptions.) Unacknowledged messages that have been received from a nondurable subscription will be dropped when the JMSContext is closed.

If you use a queue or a durable subscription, you can use the JMSContext.recover method to stop a nontransacted JMSContext and restart it with its first unacknowledged message. In effect, the JMSContext's series of delivered messages is reset to the point after its last acknowledged message. The messages it now delivers may be different from those that were originally delivered, if messages have expired or if higher-priority messages have arrived. For a consumer on a nondurable subscription, the provider may drop unacknowledged messages when the JMSContext.recover method is called.

The sample program in Acknowledging Messages demonstrates two ways to ensure that a message will not be acknowledged until processing of the message is complete.

Specifying Options for Sending Messages

You can set a number of options when you send a message. These options enable you to perform the tasks described in the following topics:

Specifying Message Persistence

Jakarta Messaging supports two delivery modes specifying whether messages are lost if the Messaging provider fails. These delivery modes are fields of the DeliveryMode interface.

  • The default delivery mode, PERSISTENT, instructs the Messaging provider to take extra care to ensure that a message is not lost in transit in case of a Messaging provider failure. A message sent with this delivery mode is logged to stable storage when it is sent.

  • The NON_PERSISTENT delivery mode does not require the Messaging provider to store the message or otherwise guarantee that it is not lost if the provider fails.

To specify the delivery mode, use the setDeliveryMode method of the JMSProducer interface to set the delivery mode for all messages sent by that producer.

You can use method chaining to set the delivery mode when you create a producer and send a message. The following call creates a producer with a NON_PERSISTENT delivery mode and uses it to send a message:

context.createProducer()
       .setDeliveryMode(DeliveryMode.NON_PERSISTENT).send(dest, msg);

If you do not specify a delivery mode, the default is PERSISTENT. Using the NON_PERSISTENT delivery mode may improve performance and reduce storage overhead, but you should use it only if your application can afford to miss messages.

Setting Message Priority Levels

You can use message priority levels to instruct the Messaging provider to deliver urgent messages first. Use the setPriority method of the JMSProducer interface to set the priority level for all messages sent by that producer.

You can use method chaining to set the priority level when you create a producer and send a message. For example, the following call sets a priority level of 7 for a producer and then sends a message:

context.createProducer().setPriority(7).send(dest, msg);

The ten levels of priority range from 0 (lowest) to 9 (highest). If you do not specify a priority level, the default level is 4. A Messaging provider tries to deliver higher-priority messages before lower-priority ones, but does not have to deliver messages in exact order of priority.

Allowing Messages to Expire

By default, a message never expires. If a message will become obsolete after a certain period, however, you may want to set an expiration time. Use the setTimeToLive method of the JMSProducer interface to set a default expiration time for all messages sent by that producer.

For example, a message that contains rapidly changing data such as a stock price will become obsolete after a few minutes, so you might configure messages to expire after that time.

You can use method chaining to set the time to live when you create a producer and send a message. For example, the following call sets a time to live of five minutes for a producer and then sends a message:

context.createProducer().setTimeToLive(300000).send(dest, msg);

If the specified timeToLive value is 0, the message never expires.

When the message is sent, the specified timeToLive is added to the current time to give the expiration time. Any message not delivered before the specified expiration time is destroyed. The destruction of obsolete messages conserves storage and computing resources.

Specifying a Delivery Delay

You can specify a length of time that must elapse after a message is sent before the Messaging provider delivers the message. Use the setDeliveryDelay method of the JMSProducer interface to set a delivery delay for all messages sent by that producer.

You can use method chaining to set the delivery delay when you create a producer and send a message. For example, the following call sets a delivery delay of 3 seconds for a producer and then sends a message:

context.createProducer().setDeliveryDelay(3000).send(dest, msg);

Using JMSProducer Method Chaining

The setter methods on the JMSProducer interface return JMSProducer objects, so you can use method chaining to create a producer, set multiple properties, and send a message. For example, the following chained method calls create a producer, set a user-defined property, set the expiration, delivery mode, and priority for the message, and then send a message to a queue:

context.createProducer()
        .setProperty("MyProperty", "MyValue")
        .setTimeToLive(10000)
        .setDeliveryMode(NON_PERSISTENT)
        .setPriority(2)
        .send(queue, body);

You can also call the JMSProducer methods to set properties on a message and then send the message in a separate send method call. You can also set message properties directly on a message.

Creating Temporary Destinations

Normally, you create JMS destinations (queues and topics) administratively rather than programmatically. Your Messaging provider includes a tool to create and remove destinations, and it is common for destinations to be long-lasting.

Jakarta Messaging also enables you to create destinations (TemporaryQueue and TemporaryTopic objects) that last only for the duration of the connection in which they are created. You create these destinations dynamically using the JMSContext.createTemporaryQueue and the JMSContext.createTemporaryTopic methods, as in the following example:

TemporaryTopic replyTopic = context.createTemporaryTopic();

The only message consumers that can consume from a temporary destination are those created by the same connection that created the destination. Any message producer can send to the temporary destination. If you close the connection to which a temporary destination belongs, the destination is closed and its contents are lost.

You can use temporary destinations to implement a simple request/reply mechanism. If you create a temporary destination and specify it as the value of the JMSReplyTo message header field when you send a message, then the consumer of the message can use the value of the JMSReplyTo field as the destination to which it sends a reply. The consumer can also reference the original request by setting the JMSCorrelationID header field of the reply message to the value of the JMSMessageID header field of the request. For example, an onMessage method can create a JMSContext so that it can send a reply to the message it receives. It can use code such as the following:

replyMsg = context.createTextMessage("Consumer processed message: "
        + msg.getText());
replyMsg.setJMSCorrelationID(msg.getJMSMessageID());
context.createProducer().send((Topic) msg.getJMSReplyTo(), replyMsg);

Using Jakarta Messaging Local Transactions

A transaction groups a series of operations into an atomic unit of work. If any one of the operations fails, the transaction can be rolled back, and the operations can be attempted again from the beginning. If all the operations succeed, the transaction can be committed.

In an application client or a Java SE client, you can use local transactions to group message sends and receives. You use the JMSContext.commit method to commit a transaction. You can send multiple messages in a transaction, and the messages will not be added to the queue or topic until the transaction is committed. If you receive multiple messages in a transaction, they will not be acknowledged until the transaction is committed.

You can use the JMSContext.rollback method to roll back a transaction. A transaction rollback means that all produced messages are destroyed and all consumed messages are recovered and redelivered unless they have expired (see Allowing Messages to Expire).

A transacted session is always involved in a transaction. To create a transacted session, call the createContext method as follows:

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

As soon as the commit or the rollback method is called, one transaction ends and another transaction begins. Closing a transacted session rolls back its transaction in progress, including any pending sends and receives.

In an application running in the Jakarta EE web or enterprise bean container, you cannot use local transactions. Instead, you use Jakarta Transactions, described in Using Jakarta Messaging in Jakarta EE Applications.

You can combine several sends and receives in a single Jakarta Messaging local transaction, so long as they are all performed using the same JMSContext.

Do not use a single transaction if you use a request/reply mechanism, in which you send a message and then receive a reply to that message. If you try to use a single transaction, the program will hang, because the send cannot take place until the transaction is committed. The following code fragment illustrates the problem:

// Don't do this!
outMsg.setJMSReplyTo(replyQueue);
context.createProducer().send(outQueue, outMsg);
consumer = context.createConsumer(replyQueue);
inMsg = consumer.receive();
context.commit();

Because a message sent during a transaction is not actually sent until the transaction is committed, the transaction cannot contain any receives that depend on that message’s having been sent.

The production and the consumption of a message cannot both be part of the same transaction. The reason is that the transactions take place between the clients and the Messaging provider, which intervenes between the production and the consumption of the message. Figure 8, “Using Jakarta Messaging Local Transactions” illustrates this interaction.

Diagram of local transactions, showing separate transactions for sending and consuming a message
Figure 8. Using Jakarta Messaging Local Transactions

The sending of one or more messages to one or more destinations by Client 1 can form a single transaction, because it forms a single set of interactions with the Messaging provider using a single JMSContext. Similarly, the receiving of one or more messages from one or more destinations by Client 2 also forms a single transaction using a single JMSContext. But because the two clients have no direct interaction and are using two different JMSContext objects, no transactions can take place between them.

Another way of putting this is that a transaction is a contract between a client and a Messaging provider that defines whether a message is sent to a destination or whether a message is received from the destination. It is not a contract between the sending client and the receiving client.

This is the fundamental difference between messaging and synchronized processing. Instead of tightly coupling the sender and the receiver of a message, JMS couples the sender of a message with the destination, and it separately couples the destination with the receiver of the message. Therefore, while the sends and receives each have a tight coupling with the Messaging provider, they do not have any coupling with each other.

When you create a JMSContext, you can specify whether it is transacted by using the JMSContext.SESSION_TRANSACTED argument to the createContext method. For example:

try (JMSContext context = connectionFactory.createContext(
        JMSContext.SESSION_TRANSACTED);) {
    ...
}

The commit and the rollback methods for local transactions are associated with the session that underlies the JMSContext. You can combine operations on more than one queue or topic, or on a combination of queues and topics, in a single transaction if you use the same session to perform the operations. For example, you can use the same JMSContext to receive a message from a queue and send a message to a topic in the same transaction.

The example in Using Local Transactions shows how to use Jakarta Messaging local transactions.

Sending Messages Asynchronously

Normally, when you send a persistent message, the send method blocks until the Messaging provider confirms that the message was sent successfully. The asynchronous send mechanism allows your application to send a message and continue work while waiting to learn whether the send completed.

This feature is currently available only in application clients and Java SE clients.

Sending a message asynchronously involves supplying a callback object. You specify a CompletionListener with an onCompletion method. For example, the following code instantiates a CompletionListener named SendListener. It then calls the setAsync method to specify that sends from this producer should be asynchronous and should use the specified listener:

CompletionListener listener = new SendListener();
context.createProducer().setAsync(listener).send(dest, message);

The CompletionListener class must implement two methods, onCompletion and onException. The onCompletion method is called if the send succeeds, and the onException method is called if it fails. A simple implementation of these methods might look like this:

@Override
public void onCompletion(Message message) {
    System.out.println("onCompletion method: Send has completed.");
}

@Override
public void onException(Message message, Exception e) {
    System.out.println("onException method: send failed: " + e.toString());
    System.out.println("Unsent message is: \n" + message);
}

Using Jakarta Messaging in Jakarta EE Applications

This section describes how using Jakarta Messaging in enterprise bean applications or web applications differs from using it in application clients.

Overview of Using Jakarta Messaging

A general rule in the Jakarta EE platform specification applies to all Jakarta EE components that use Jakarta Messaging within enterprise bean or web containers: Application components in the web and enterprise bean containers must not attempt to create more than one active (not closed) Session object per connection. Multiple JMSContext objects are permitted, however, since they combine a single connection and a single session.

This rule does not apply to application clients. The application client container supports the creation of multiple sessions for each connection.

Creating Resources for Jakarta EE Applications

You can use annotations to create application-specific connection factories and destinations for Jakarta EE enterprise bean or web components. The resources you create in this way are visible only to the application for which you create them.

You can also use deployment descriptor elements to create these resources. Elements specified in the deployment descriptor override elements specified in annotations. See Packaging Applications for basic information about deployment descriptors. You must use a deployment descriptor to create application-specific resources for application clients.

To create a destination, use a @JMSDestinationDefinition annotation like the following on a class:

@JMSDestinationDefinition(
    name = "java:app/jms/myappTopic",
    interfaceName = "jakarta.jms.Topic",
    destinationName = "MyPhysicalAppTopic"
  )

The name, interfaceName, and destinationName elements are required. You can optionally specify a description element. To create multiple destinations, enclose them in a @JMSDestinationDefinitions annotation, separated by commas.

To create a connection factory, use a @JMSConnectionFactoryDefinition annotation like the following on a class:

@JMSConnectionFactoryDefinition(
    name="java:app/jms/MyConnectionFactory"
)

The name element is required. You can optionally specify a number of other elements, such as clientId if you want to use the connection factory for durable subscriptions, or description. If you do not specify the interfaceName element, the default interface is jakarta.jms.ConnectionFactory. To create multiple connection factories, enclose them in a @JMSConnectionFactoryDefinitions annotation, separated by commas.

You need to specify the annotation only once for a given application, in any of the components.

If your application contains one or more message-driven beans, you may want to place the annotation on one of the message-driven beans. If you place the annotation on a sending component such as an application client, you need to specify the mappedName element to look up the topic, instead of using the destinationLookup property of the activation configuration specification.

When you inject the resource into a component, use the value of the name element in the definition annotation as the value of the lookup element in the @Resource annotation:

@Resource(lookup = "java:app/jms/myappTopic")
private Topic topic;

The following portable JNDI namespaces are available. Which ones you can use depends on how your application is packaged.

  • java:global: Makes the resource available to all deployed applications

  • java:app: Makes the resource available to all components in all modules in a single application

  • java:module: Makes the resource available to all components within a given module (for example, all enterprise beans within a Jakarta Enterprise Beans module)

  • java:comp: Makes the resource available to a single component only (except in a web application, where it is equivalent to java:module)

See the API documentation for details on these annotations. The examples in Sending and Receiving Messages Using a Simple Web Application, Sending Messages from a Session Bean to an MDB, and Using an Entity to Join Messages from Two MDBs all use the @JMSDestinationDefinition annotation. The other JMS examples do not use these annotations. The examples that consist only of application clients are not deployed in the application server and must therefore communicate with each other using administratively created resources that exist outside of individual applications.

Using Resource Injection in Enterprise Bean or Web Components

You may use resource injection to inject both administered objects and JMSContext objects in Jakarta EE applications.

Injecting a ConnectionFactory, Queue, or Topic

Normally, you use the @Resource annotation to inject a ConnectionFactory, Queue, or Topic into your Jakarta EE application. These objects must be created administratively before you deploy your application. You may want to use the default connection factory, whose JNDI name is java:comp/DefaultJMSConnectionFactory.

When you use resource injection in an application client component, you normally declare the Messaging resource static:

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

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

However, when you use this annotation in a session bean, a message-driven bean, or a web component, do not declare the resource static:

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

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

If you declare the resource static in these components, runtime errors will result.

Injecting a JMSContext Object

To access a JMSContext object in an enterprise bean or web component, instead of injecting the ConnectionFactory resource and then creating a JMSContext, you can use the @Inject and @JMSConnectionFactory annotations to inject a JMSContext. To use the default connection factory, use code like the following:

@Inject
private JMSContext context1;

To use your own connection factory, use code like the following:

@Inject
@JMSConnectionFactory("jms/MyConnectionFactory")
private JMSContext context2;

Using Jakarta EE Components to Produce and to Synchronously Receive Messages

An application that produces messages or synchronously receives them can use a Jakarta EE web or Jakarta Enterprise Beans component, such as a managed bean, a servlet, or a session bean, to perform these operations. The example in Sending Messages from a Session Bean to an MDB uses a stateless session bean to send messages to a topic. The example in Sending and Receiving Messages Using a Simple Web Application uses managed beans to produce and to consume messages.

Because a synchronous receive with no specified timeout ties up server resources, this mechanism usually is not the best application design for a web or Jakarta Enterprise Beans component. Instead, use a synchronous receive that specifies a timeout value, or use a message-driven bean to receive messages asynchronously. For details about synchronous receives, see Jakarta Messaging Message Consumers.

Using Jakarta Messaging in a Jakarta EE component is in many ways similar to using it in an application client. The main differences are the areas of resource management and transactions.

Managing Jakarta Messaging Resources in Web and Jakarta Enterprise Beans Components

The Jakarta Messaging resources are a connection and a session, usually combined in a JMSContext object. In general, it is important to release Messaging resources when they are no longer being used. Here are some useful practices to follow.

  • If you wish to maintain a Messaging resource only for the life span of a business method, use a try-with-resources statement to create the JMSContext so that it will be closed automatically at the end of the try block.

  • To maintain a Messaging resource for the duration of a transaction or request, inject the JMSContext as described in Injecting a JMSContext Object. This will also cause the resource to be released when it is no longer needed.

  • If you would like to maintain a Messaging resource for the life span of an enterprise bean instance, you can use a @PostConstruct callback method to create the resource and a @PreDestroy callback method to close the resource. However, there is normally no need to do this, since application servers usually maintain a pool of connections. If you use a stateful session bean and you wish to maintain the Messaging resource in a cached state, you must close the resource in a @PrePassivate callback method and set its value to null, and you must create it again in a @PostActivate callback method.

Managing Transactions in Session Beans

Instead of using local transactions, you use Jakarta transactions. You can use either container-managed transactions or bean-managed transactions. Normally, you use container-managed transactions for bean methods that perform sends or receives, allowing the enterprise bean container to handle transaction demarcation. Because container-managed transactions are the default, you do not have to specify them.

You can use bean-managed transactions and the jakarta.transaction.UserTransaction interface’s transaction demarcation methods, but you should do so only if your application has special requirements and you are an expert in using transactions. Usually, container-managed transactions produce the most efficient and correct behavior. This tutorial does not provide any examples of bean-managed transactions.

Using Message-Driven Beans to Receive Messages Asynchronously

The sections What Is a Message-Driven Bean? and How Does Jakarta Messaging Work with the Jakarta EE Platform? describe how the Jakarta EE platform supports a special kind of enterprise bean, the message-driven bean, which allows Jakarta EE applications to process Jakarta Messaging messages asynchronously. Other Jakarta EE web and Jakarta Enterprise Beans components allow you to send messages and to receive them synchronously but not asynchronously.

A message-driven bean is a message listener to which messages can be delivered from either a queue or a topic. The messages can be sent by any Jakarta EE component (from an application client, another enterprise bean, or a web component) or from an application or a system that does not use Jakarta EE technology.

A message-driven bean class has the following requirements.

  • It must be annotated with the @MessageDriven annotation if it does not use a deployment descriptor.

  • The class must be defined as public, but not as abstract or final.

  • It must contain a public constructor with no arguments.

It is recommended, but not required, that a message-driven bean class implement the message listener interface for the message type it supports. A bean that supports Jakarta Messaging implements the jakarta.jms.MessageListener interface, which means that it must provide an onMessage method with the following signature:

void onMessage(Message inMessage)

The onMessage method is called by the bean’s container when a message has arrived for the bean to service. This method contains the business logic that handles the processing of the message. It is the message-driven bean’s responsibility to parse the message and perform the necessary business logic.

A message-driven bean differs from an application client’s message listener in the following ways.

  • In an application client, you must create a JMSContext, then create a JMSConsumer, then call setMessageListener to activate the listener. For a message-driven bean, you need only define the class and annotate it, and the enterprise bean container creates it for you.

  • The bean class uses the @MessageDriven annotation, which typically contains an activationConfig element containing @ActivationConfigProperty annotations that specify properties used by the bean or the connection factory. These properties can include the connection factory, a destination type, a durable subscription, a message selector, or an acknowledgment mode. Some of the examples in Jakarta Messaging Examples set these properties. You can also set the properties in the deployment descriptor.

  • The application client container has only one instance of a MessageListener, which is called on a single thread at a time. A message-driven bean, however, may have multiple instances, configured by the container, which may be called concurrently by multiple threads (although each instance is called by only one thread at a time). Message-driven beans may therefore allow much faster processing of messages than message listeners.

  • You do not need to specify a message acknowledgment mode unless you use bean-managed transactions. The message is consumed in the transaction in which the onMessage method is invoked.

@ActivationConfigProperty Settings for Message-Driven Beans lists the activation configuration properties defined by the Jakarta Messaging specification.

@ActivationConfigProperty Settings for Message-Driven Beans
Property Name Description

acknowledgeMode

Acknowledgment mode, used only for bean-managed transactions; the default is Auto-acknowledge (Dups-ok-acknowledge is also permitted)

destinationLookup

The lookup name of the queue or topic from which the bean will receive messages

destinationType

Either jakarta.jms.Queue or jakarta.jms.Topic

subscriptionDurability

For durable subscriptions, set the value to Durable; see Creating Durable Subscriptions for more information

clientId

For durable subscriptions, the client ID for the connection (optional)

subscriptionName

For durable subscriptions, the name of the subscription

messageSelector

A string that filters messages; see Jakarta Messaging Message Selectors for information

connectionFactoryLookup

The lookup name of the connection factory to be used to connect to the Messaging provider from which the bean will receive messages

For example, here is the message-driven bean used in Receiving Messages Asynchronously Using a Message-Driven Bean:

@MessageDriven(activationConfig = {
    @ActivationConfigProperty(propertyName = "destinationLookup",
            propertyValue = "jms/MyQueue"),
    @ActivationConfigProperty(propertyName = "destinationType",
            propertyValue = "jakarta.jms.Queue")
})
public class SimpleMessageBean implements MessageListener {

    @Resource
    private MessageDrivenContext mdc;
    static final Logger logger = Logger.getLogger("SimpleMessageBean");

    public SimpleMessageBean() {
    }

    @Override
    public void onMessage(Message inMessage) {

        try {
            if (inMessage instanceof TextMessage) {
                logger.log(Level.INFO,
                        "MESSAGE BEAN: Message received: {0}",
                        inMessage.getBody(String.class));
            } else {
                logger.log(Level.WARNING,
                        "Message of wrong type: {0}",
                        inMessage.getClass().getName());
            }
        } catch (JMSException e) {
            logger.log(Level.SEVERE,
                    "SimpleMessageBean.onMessage: JMSException: {0}",
                    e.toString());
            mdc.setRollbackOnly();
        }
    }
}

If Jakarta Messaging is integrated with the application server using a resource adapter, the Messaging resource adapter handles these tasks for the enterprise bean container.

The bean class commonly injects a MessageDrivenContext resource, which provides some additional methods you can use for transaction management (setRollbackOnly, for example):

    @Resource
    private MessageDrivenContext mdc;

A message-driven bean never has a local or remote interface. Instead, it has only a bean class.

A message-driven bean is similar in some ways to a stateless session bean: Its instances are relatively short-lived and retain no state for a specific client. The instance variables of the message-driven bean instance can contain some state across the handling of client messages: for example, an open database connection, or an object reference to an enterprise bean object.

Like a stateless session bean, a message-driven bean can have many interchangeable instances running at the same time. The container can pool these instances to allow streams of messages to be processed concurrently. The container attempts to deliver messages in chronological order when that would not impair the concurrency of message processing, but no guarantees are made as to the exact order in which messages are delivered to the instances of the message-driven bean class. If message order is essential to your application, you may want to configure your application server to use just one instance of the message-driven bean.

For details on the lifecycle of a message-driven bean, see The Lifecycle of a Message-Driven Bean.

Managing Jakarta Transactions

Jakarta EE application clients and Java SE clients use JMS local transactions (described in Using Jakarta Messaging Local Transactions), which allow the grouping of sends and receives within a specific Messaging session. Jakarta EE applications that run in the web or enterprise bean container commonly use Jakarta Transactions to ensure the integrity of accesses to external resources. The key difference between a Jakarta transaction and a Jakarta Messaging local transaction is that a Jakarta transaction is controlled by the application server’s transaction managers. Jakarta transactions may be distributed, which means that they can encompass multiple resources in the same transaction, such as a Messaging provider and a database.

For example, distributed transactions allow multiple applications to perform atomic updates on the same database, and they allow a single application to perform atomic updates on multiple databases.

In a Jakarta EE application that uses Jakarta Messaging, you can use transactions to combine message sends or receives with database updates and other resource manager operations. You can access resources from multiple application components within a single transaction. For example, a servlet can start a transaction, access multiple databases, invoke an enterprise bean that sends a Jakarta Messaging message, invoke another enterprise bean that modifies an EIS system using the Connectors, and finally commit the transaction. Your application cannot, however, both send a Jakarta Messaging message and receive a reply to it within the same transaction.

Jakarta Transactions within the enterprise bean and web containers can be either of two kinds.

  • Container-managed transactions: The container controls the integrity of your transactions without your having to call commit or rollback. Container-managed transactions are easier to use than bean-managed transactions. You can specify appropriate transaction attributes for your enterprise bean methods.

    Use the Required transaction attribute (the default) to ensure that a method is always part of a transaction. If a transaction is in progress when the method is called, the method will be part of that transaction; if not, a new transaction will be started before the method is called and will be committed when the method returns. See Transaction Attributes for more information.

  • Bean-managed transactions: You can use these in conjunction with the jakarta.transaction.UserTransaction interface, which provides its own commit and rollback methods you can use to delimit transaction boundaries. Bean-managed transactions are recommended only for those who are experienced in programming transactions.

You can use either container-managed transactions or bean-managed transactions with message-driven beans. To ensure that all messages are received and handled within the context of a transaction, use container-managed transactions and use the Required transaction attribute (the default) for the onMessage method.

When you use container-managed transactions, you can call the following MessageDrivenContext methods.

  • setRollbackOnly: Use this method for error handling. If an exception occurs, setRollbackOnly marks the current transaction so that the only possible outcome of the transaction is a rollback.

  • getRollbackOnly: Use this method to test whether the current transaction has been marked for rollback.

If you use bean-managed transactions, the delivery of a message to the onMessage method takes place outside the Jakarta transaction context. The transaction begins when you call the UserTransaction.begin method within the onMessage method, and it ends when you call UserTransaction.commit or UserTransaction.rollback. Any call to the Connection.createSession method must take place within the transaction.

Using bean-managed transactions allows you to process the message by using more than one transaction or to have some parts of the message processing take place outside a transaction context. However, if you use container-managed transactions, the message is received by the MDB and processed by the onMessage method within the same transaction. It is not possible to achieve this behavior with bean-managed transactions.

When you create a JMSContext in a Jakarta transaction (in the web or enterprise bean container), the container ignores any arguments you specify, because it manages all transactional properties. When you create a JMSContext in the web or enterprise bean container and there is no Jakarta transaction, the value (if any) passed to the createContext method should be JMSContext.AUTO_ACKNOWLEDGE or JMSContext.DUPS_OK_ACKNOWLEDGE.

When you use container-managed transactions, you normally use the Required transaction attribute (the default) for your enterprise bean’s business methods.

You do not specify the activation configuration property acknowledgeMode when you create a message-driven bean that uses container-managed transactions. The container acknowledges the message automatically when it commits the transaction.

If a message-driven bean uses bean-managed transactions, the message receipt cannot be part of the bean-managed transaction. You can set the activation configuration property acknowledgeMode to Auto-acknowledge or Dups-ok-acknowledge to specify how you want the message received by the message-driven bean to be acknowledged.

If the onMessage method throws a RuntimeException, the container does not acknowledge processing the message. In that case, the Messaging provider will redeliver the unacknowledged message in the future.

Further Information about Jakarta Messaging

For more information about Jakarta Messaging, see