Skip to content

Commit 7491efa

Browse files
committed
Clarify acknowledge mode semantics and transaction recommendations
Issue: SPR-13278 (cherry picked from commit 4012b4d)
1 parent d2447ce commit 7491efa

File tree

2 files changed

+45
-34
lines changed

2 files changed

+45
-34
lines changed

spring-jms/src/main/java/org/springframework/jms/listener/AbstractMessageListenerContainer.java

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,19 @@
3131
import org.springframework.util.ErrorHandler;
3232

3333
/**
34-
* Abstract base class for message listener containers. Can either host
35-
* a standard JMS {@link javax.jms.MessageListener} or a Spring-specific
36-
* {@link SessionAwareMessageListener}.
34+
* Abstract base class for Spring message listener container implementations.
35+
* Can either host a standard JMS {@link javax.jms.MessageListener} or Spring's
36+
* {@link SessionAwareMessageListener} for actual message processing.
3737
*
38-
* <p>Usually holds a single JMS {@link Connection} that all listeners are
39-
* supposed to be registered on, which is the standard JMS way of managing
40-
* listeners. Can alternatively also be used with a fresh Connection per
41-
* listener, for J2EE-style XA-aware JMS messaging. The actual registration
42-
* process is up to concrete subclasses.
38+
* <p>Usually holds a single JMS {@link Connection} that all listeners are supposed
39+
* to be registered on, which is the standard JMS way of managing listener sessions.
40+
* Can alternatively also be used with a fresh Connection per listener, for Java EE
41+
* style XA-aware JMS messaging. The actual registration process is up to concrete
42+
* subclasses.
4343
*
44-
* <p><b>NOTE:</b> The default behavior of this message listener container
45-
* is to <b>never</b> propagate an exception thrown by a message listener up to
46-
* the JMS provider. Instead, it will log any such exception at the error level.
44+
* <p><b>NOTE:</b> The default behavior of this message listener container is to
45+
* <b>never</b> propagate an exception thrown by a message listener up to the JMS
46+
* provider. Instead, it will log any such exception at the error level.
4747
* This means that from the perspective of the attendant JMS provider no such
4848
* listener will ever fail. However, if error handling is necessary, then
4949
* any implementation of the {@link ErrorHandler} strategy may be provided to
@@ -56,37 +56,48 @@
5656
* <li>"sessionAcknowledgeMode" set to "AUTO_ACKNOWLEDGE" (default):
5757
* This mode is container-dependent: For {@link DefaultMessageListenerContainer},
5858
* it means automatic message acknowledgment <i>before</i> listener execution, with
59-
* no redelivery in case of an exception. For {@link SimpleMessageListenerContainer},
59+
* no redelivery in case of an exception and no redelivery in case of other listener
60+
* execution interruptions either. For {@link SimpleMessageListenerContainer},
6061
* it means automatic message acknowledgment <i>after</i> listener execution, with
61-
* redelivery in case of an exception thrown, as defined by the JMS specification.
62-
* In order to consistently achieve the latter behavior with any container variant,
63-
* consider setting "sessionTransacted" to "true" instead.
62+
* no redelivery in case of a user exception thrown but potential redelivery in case
63+
* of the JVM dying during listener execution. In order to consistently arrange for
64+
* redelivery with any container variant, consider "CLIENT_ACKNOWLEDGE" mode or -
65+
* preferably - setting "sessionTransacted" to "true" instead.
66+
* <li>"sessionAcknowledgeMode" set to "DUPS_OK_ACKNOWLEDGE":
67+
* <i>Lazy</i> message acknowledgment during ({@link DefaultMessageListenerContainer})
68+
* or shortly after ({@link SimpleMessageListenerContainer}) listener execution;
69+
* no redelivery in case of a user exception thrown but potential redelivery in case
70+
* of the JVM dying during listener execution. In order to consistently arrange for
71+
* redelivery with any container variant, consider "CLIENT_ACKNOWLEDGE" mode or -
72+
* preferably - setting "sessionTransacted" to "true" instead.
6473
* <li>"sessionAcknowledgeMode" set to "CLIENT_ACKNOWLEDGE":
6574
* Automatic message acknowledgment <i>after</i> successful listener execution;
66-
* best-effort redelivery in case of exception thrown.
67-
* <li>"sessionAcknowledgeMode" set to "DUPS_OK_ACKNOWLEDGE":
68-
* <i>Lazy</i> message acknowledgment during or after listener execution;
69-
* <i>potential redelivery</i> in case of exception thrown.
75+
* best-effort redelivery in case of a user exception thrown as well as in case
76+
* of other listener execution interruptions (such as the JVM dying).
7077
* <li>"sessionTransacted" set to "true":
7178
* Transactional acknowledgment after successful listener execution;
72-
* <i>guaranteed redelivery</i> in case of exception thrown.
79+
* <i>guaranteed redelivery</i> in case of a user exception thrown as well as
80+
* in case of other listener execution interruptions (such as the JVM dying).
7381
* </ul>
74-
* The exact behavior might vary according to the concrete listener container
75-
* and JMS provider used.
7682
*
77-
* <p>There are two solutions to the duplicate processing problem:
83+
* <p>There are two solutions to the duplicate message processing problem:
7884
* <ul>
7985
* <li>Either add <i>duplicate message detection</i> to your listener, in the
8086
* form of a business entity existence check or a protocol table check. This
8187
* usually just needs to be done in case of the JMSRedelivered flag being
82-
* set on the incoming message (else just process straightforwardly).
83-
* <li>Or wrap the <i>entire processing with an XA transaction</i>, covering the
84-
* reception of the message as well as the execution of the message listener.
85-
* This is only supported by {@link DefaultMessageListenerContainer}, through
86-
* specifying a "transactionManager" (typically a
88+
* set on the incoming message (otherwise just process straightforwardly).
89+
* Note that with "sessionTransacted" set to "true", duplicate messages will
90+
* only appear in case of the JVM dying at the most unfortunate point possible
91+
* (i.e. after your business logic executed but before the JMS part got committed),
92+
* so duplicate message detection is just there to cover a corner case.
93+
* <li>Or wrap your <i>entire processing with an XA transaction</i>, covering the
94+
* reception of the JMS message as well as the execution of the business logic in
95+
* your message listener (including database operations etc). This is only
96+
* supported by {@link DefaultMessageListenerContainer}, through specifying
97+
* an external "transactionManager" (typically a
8798
* {@link org.springframework.transaction.jta.JtaTransactionManager}, with
88-
* a corresponding XA-aware JMS {@link javax.jms.ConnectionFactory} passed in as
89-
* "connectionFactory").
99+
* a corresponding XA-aware JMS {@link javax.jms.ConnectionFactory} passed in
100+
* as "connectionFactory").
90101
* </ul>
91102
* Note that XA transaction coordination adds significant runtime overhead,
92103
* so it might be feasible to avoid it unless absolutely necessary.
@@ -102,7 +113,7 @@
102113
* <li>Alternatively, specify a
103114
* {@link org.springframework.transaction.jta.JtaTransactionManager} as
104115
* "transactionManager" for a fully XA-aware JMS provider - typically when
105-
* running on a J2EE server, but also for other environments with a JTA
116+
* running on a Java EE server, but also for other environments with a JTA
106117
* transaction manager present. This will give full "exactly-once" guarantees
107118
* without custom duplicate message checks, at the price of additional
108119
* runtime processing overhead.
@@ -709,7 +720,7 @@ protected void invokeErrorHandler(Throwable ex) {
709720
if (this.errorHandler != null) {
710721
this.errorHandler.handleError(ex);
711722
}
712-
else if (logger.isWarnEnabled()) {
723+
else {
713724
logger.warn("Execution of JMS message listener failed, and no ErrorHandler has been set.", ex);
714725
}
715726
}
@@ -721,7 +732,6 @@ else if (logger.isWarnEnabled()) {
721732
*/
722733
@SuppressWarnings("serial")
723734
private static class MessageRejectedWhileStoppingException extends RuntimeException {
724-
725735
}
726736

727737
}

spring-jms/src/main/java/org/springframework/jms/listener/SimpleMessageListenerContainer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848
* on acknowledge modes and transaction options. Note that this container
4949
* exposes standard JMS behavior for the default "AUTO_ACKNOWLEDGE" mode:
5050
* that is, automatic message acknowledgment after listener execution,
51-
* with redelivery in case of an exception thrown.
51+
* with no redelivery in case of a user exception thrown but potential
52+
* redelivery in case of the JVM dying during listener execution.
5253
*
5354
* <p>For a different style of MessageListener handling, through looped
5455
* {@code MessageConsumer.receive()} calls that also allow for

0 commit comments

Comments
 (0)