JMS (Java Message Service) is an interfacing technology to access message servers from within your Java or J2EE application. During the last years, JMS has gained enormous popularity in the EAI (enterprise application integration) business because of its ability to provide so-called reliable messaging. This tech tip takes a closer look at what 'reliable' messaging means and how to achieve it. As we will see, it all depends on how you configure and use your JMS. But first some vocabulary.
In the context of this tech tip, we will distinguish between different delivery guarantees of a message:
: in this case, there is actually no guarantee. A message may disappear before it can be consumed.
At least once
: a message is never lost in the system, but can be delivered to the receiver more than once (and hence be consumed more than once). This implies that the receiver needs to be programmed with this in mind.
: a message is never lost and only delivered once. This is the real reliable messaging mode.
Reliable Messaging with JMS
Whether or not your messaging application is reliable depends on the following parameters:
- Whether or not messages are persisted in the JMS server.
- Which acknowledgement mode is used by the receiver.
Clearly, if messages are not persisted then there is always the possibility of losing a message (if the server crashes before the message can be delivered). So in that case, you can only count on Maybe semantics.
However, even if messages are persisted there is still the possibility of losing messages (in this tech tip, 'losing' a message is somewhat broader than as defined in the JMS specification: we say that a message is lost if it fails to be processed in the DBMS of the receiver). For persistent messaging, all depends on the acknowledgement mode. The acknowledgement of a message triggers the deletion of the message on the server.
Message Acknowledgement Modes
The following modes exist in JMS:
: this means that a message is acknowledged (deleted) as soon as the receiver gets it.
: this means that the receiver has to explicitly tell the server when the message is no longer needed.
: if the receiver gets the messages within the scope of a JTA transaction then acknowledgement is done if and only if the transaction commits.
Achieving Exactly-Once Guarantees
The following table summarizes the message guarantees for each possible combination of these parameters:
As you can see, in order to have reliable messaging you need to have transactions and persistence enabled. Otherwise, be prepared to deal with either message loss or duplicate messages...
An Example of How Things Can Go Wrong
It is tempting to believe that it suffices to use JMS in order to achieve fully reliable messaging, but this is not true.
Let's consider the following example of where things can go wrong. Assume that a message comes in on the JMS queue (say, a payment order) and we want to process the payment in the database.
- The message is taken off the queue
- The message is processed by our Java program
- The results are put in the database
Let's now focus on the following questions:
- Can messages be lost?
- Can messages be received twice?
The answer to each question will depend on the acknowledgement mode for JMS. Again, the possible values are: automatic, explicit and transactional.
With automatic acknowledgement, the JMS message is deleted from the queue as soon as it is taken off. Clearly, this allows message loss if our program crashes in step 2. In that case, the payment order is lost forever. Hence, we have message loss.
With explicit acknowledgement, the JMS message is not deleted from the queue until our program tells it to do so.
When could we tell the JMS to delete the message? If done before step 2, then a crash in 2 means message loss again. If done during step 2, then a crash will also lose the message since the database has not been changed.
If done after step 3, then a crash could happen between 3 and before we can delete the message. In that case, the message will be redelivered upon restart and will be re-processed and re-posted to the database. That is: duplicates.
With transactional acknowledgement, there are two possibilities: connector-level JMS and JDBC transactions or JTA/XA. When JMS is used in transactional mode, the deletion of a message is only done if and only if the transaction commits. JDBC implies that the database is updated only if the transaction commits.
For connector-level transactions, we will have two distinct transactions: one for JMS and one for JDBC.
So the question is: how should the commits be ordered to avoid message loss and duplicates?
If the JMS transaction is committed first, a crash can happen before we commit the JDBC transaction. Consequence: message loss; the payment will never be in the database.
If the JDBC transaction is committed first, a crash can happen before we commit the JMS transaction. Consequence: duplicate message; the message is not deleted from the queue and will be redelivered later. The application will re-process it and re-post it in the database.
The only remaining possibility is: joint commit of both the JMS and the JDBC transaction, and this is exactly what JTA/XA do for you. For reliable, exactly-once messaging you really need JTA/XA.