skip to content

Explain JMS acknowledge modes in a Spring listener container and how sessionTransacted changes message redelivery on exceptions.

level: seniorimportance: should knowfreq 42%

answer

  1. AUTO_ACK -> Spring acks after successful return
  2. throw -> no ack -> redeliver (at-least-once)
  3. DUPS_OK = faster, may duplicate
  4. sessionTransacted -> receive+send atomic, rollback redelivers
  5. local tx != database; drives DLQ redelivery count

basics

~20 s

Acknowledge modes decide when a consumed message is marked done. With AUTO_ACKNOWLEDGE Spring acks after your listener returns successfully, so a thrown exception avoids the ack and the broker redelivers. With sessionTransacted=true the receive runs in a local JMS transaction that rolls back on exception, also causing redelivery.

solid answer

~40 s

JMS defines AUTO_ACKNOWLEDGE, CLIENT_ACKNOWLEDGE, and DUPS_OK_ACKNOWLEDGE, plus transacted sessions. In a Spring container the semantics are container-managed. With the default AUTO_ACKNOWLEDGE, Spring acknowledges the message AFTER the listener method returns normally; if it throws, no ack is sent, the message is not consumed, and the broker redelivers (at-least-once). DUPS_OK is a laxer, faster ack that may duplicate. CLIENT_ACKNOWLEDGE is rarely used directly since the container manages acks. Setting sessionTransacted=true makes the JMS session transactional: the container commits after success and rolls back on exception, causing redelivery and also enrolling any JmsTemplate sends done in the listener into the same local transaction (so a failure un-sends them). Both AUTO_ACK-with-rollback-on-throw and sessionTransacted give redelivery, but only sessionTransacted makes receive+send atomic and drives the broker's redelivery/DLQ counters.

code

java · 23 lines
java
@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(ConnectionFactory cf) {
    var factory = new DefaultJmsListenerContainerFactory();
    factory.setConnectionFactory(cf);
    // local JMS transaction: rollback-on-exception -> redelivery,
    // and JmsTemplate sends in the listener join the same tx
    factory.setSessionTransacted(true);
    return factory;
}

@Component
public class PaymentListener {
    private final JmsTemplate jms;
    PaymentListener(JmsTemplate jms) { this.jms = jms; }

    @JmsListener(destination = "payments")
    public void handle(Payment p) {
        jms.convertAndSend("audit", p.id());  // enrolled in the same local tx
        if (!p.valid()) {
            throw new IllegalStateException("bad payment"); // rollback -> both undone, redeliver
        }
    }
}

go deeper

for a junior

Know that throwing from the listener causes the message to be redelivered rather than lost.

for a middle

Explain AUTO vs DUPS_OK vs transacted and that Spring acks after successful return.

for a senior

Detail sessionTransacted atomicity of receive+send, redelivery-count/DLQ interaction, and idempotency needs.

for a principal

Reason about delivery guarantees end-to-end, poison-message handling, DLQ policy ownership, and where local transactions stop (no DB) mandating XA.

## Acknowledgement in JMS When a consumer receives a message, the broker keeps it until it is **acknowledged** (ack'd). Ack tells the broker "delivered successfully, you may discard it." If the consumer dies or never acks, the broker **redelivers**. The JMS `Session` is created with an acknowledgement mode: - **`AUTO_ACKNOWLEDGE`** — the session auto-acks each message. In a **plain** JMS client this happens on delivery; but in a **Spring listener container** the container controls the timing: it acknowledges **after your listener method returns successfully**. If the listener **throws**, the container does not ack, so the message is redelivered. This gives **at-least-once** delivery. - **`DUPS_OK_ACKNOWLEDGE`** — lazy/batched acks. Lower overhead and lower latency, but on failure the broker may redeliver messages that were actually processed → **duplicates**. Use only when the listener is idempotent and throughput matters. - **`CLIENT_ACKNOWLEDGE`** — the application calls `message.acknowledge()` explicitly. In Spring you rarely use this because the container manages acking; acking one message acks all previously received on that session. - **`SESSION_TRANSACTED`** — the session is transacted; acks happen at `commit()`. In Spring you set `container.setSessionAcknowledgeMode(...)` / `factory.setSessionAcknowledgeMode(...)`, or `sessionTransacted`. ## sessionTransacted=true (local JMS transaction) Setting **`sessionTransacted=true`** creates the JMS session as **transacted**. Now each receive+process cycle is a **local transaction on the broker**: 1. Message is received within the transaction. 2. Listener runs. Any `JmsTemplate.send(...)` performed inside — using the **same** transactional session — joins this transaction. 3. If the listener returns normally → container **commits** → the receive is finalized (message consumed) and the sends are released. 4. If the listener throws → container **rolls back** → the receive is undone (message redelivered) **and** any sends are discarded. This makes **consume-and-forward** patterns atomic within a single broker: you won't emit an outgoing message unless the incoming one was processed successfully. It is a **local** (single-resource) transaction — it does **not** include a database. For JMS+DB atomicity you need an external `JtaTransactionManager` (XA). ## AUTO_ACKNOWLEDGE-with-rollback vs sessionTransacted — subtle differences Both redeliver on exception, but: - **AUTO_ACK**: only the ack is withheld; there is no transaction, so listener-side sends are **not** rolled back, and redelivery counting is coarser. - **sessionTransacted**: a real rollback increments the broker's **redelivery count** (`JMSXDeliveryCount`), which drives **Dead-Letter Queue (DLQ)** policies after N attempts, and makes receive+send atomic. ## Redelivery & poison messages A message that always fails will be redelivered forever unless the broker moves it to a **DLQ** after a maximum redelivery count. Configure this on the broker/`RedeliveryPolicy`, not in Spring. Without it, a poison message can block or loop a consumer. ## Gotchas - **Duplicates are always possible** with at-least-once semantics (ack lost after processing, crash before ack). Design listeners to be **idempotent**. - Under `AUTO_ACKNOWLEDGE`, blocking a long time in the listener delays the ack and can trip client/broker timeouts. - `sessionTransacted` only covers JMS resources; developers wrongly assume it also rolls back their database writes — it does not. - Setting an **external transaction manager** and `sessionTransacted=true` together is contradictory; when a `transactionManager` is set on DMLC, the external transaction governs and you leave `sessionTransacted` off.

  • Does sessionTransacted=true roll back a database insert your listener performed?
    No. sessionTransacted is a local JMS-only transaction; it covers message receive and JMS sends, not JDBC. To make JMS and DB atomic you need an external JtaTransactionManager with XA resources.
  • A failing message keeps being redelivered forever. How do you stop the loop?
    Configure broker redelivery/DLQ policy (max redelivery count -> Dead-Letter Queue). Redelivery counting is broker-side; sessionTransacted increments JMSXDeliveryCount that the policy uses. Also make handling idempotent.

saying these in an interview costs you the question

  • Believing AUTO_ACKNOWLEDGE acks on delivery so exceptions can't cause redelivery in Spring.
  • Thinking sessionTransacted rolls back database changes.
  • Assuming exactly-once delivery instead of at-least-once + idempotency.

context