Compare DefaultMessageListenerContainer and SimpleMessageListenerContainer. When would you choose each, and how does concurrency differ?
answer
- DMLC = polling loop -> transactions + recovery + scaling
- SMLC = provider push, fixed consumers
- external/JTA TM = DMLC only
- concurrency range ignored by SMLC
- Boot/default factory build DMLC
basics
~20 sDefaultMessageListenerContainer (DMLC) is the flexible default: it supports transactions, error recovery, and dynamic scaling of consumer threads (concurrency "lower-upper"). SimpleMessageListenerContainer (SMLC) is lightweight with a fixed number of consumers and no external transaction support. Use DMLC almost always.
solid answer
~40 sBoth run @JmsListener endpoints but differ fundamentally. DMLC uses a polling loop (receive() with a timeout) on each consumer thread; this lets it support local JMS transactions AND an external JtaTransactionManager, recover automatically after broker/connection failures, and dynamically scale consumers between a lower and upper bound (setConcurrency("3-10")). SMLC registers a plain JMS MessageListener and relies on the provider's async delivery; it has a fixed consumer count, no dynamic scaling, no external transaction-manager support (only local session transactions), and weaker recovery. SMLC has slightly lower overhead and no polling gap, but you lose transactions/scaling/recovery. In practice DMLC is the near-universal choice and is what DefaultJmsListenerContainerFactory (and Spring Boot) creates. Pick SMLC only for simple, non-transactional, fixed-load cases where you want minimal overhead and provider-driven push.
code
java · 20 lines// DMLC via factory: dynamic scaling + external JTA transaction manager
@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory(
ConnectionFactory cf, JtaTransactionManager tm) {
var factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(cf);
factory.setConcurrency("3-10"); // grows 3 -> 10 under load (DMLC only)
factory.setTransactionManager(tm); // XA across JMS + DB (DMLC only)
return factory;
}
// SMLC via factory: fixed consumers, no external TM support
@Bean
public SimpleJmsListenerContainerFactory simpleFactory(ConnectionFactory cf) {
var factory = new SimpleJmsListenerContainerFactory();
factory.setConnectionFactory(cf);
factory.setConcurrency("5"); // fixed 5; a range would NOT scale
factory.setSessionTransacted(true); // only local transactions available
return factory;
}go deeper
Know DMLC is the default and more capable; SMLC is the lightweight one.
Contrast polling-loop vs provider-push and the resulting transaction/recovery/scaling differences.
Reason about receiveTimeout, recoveryInterval, idle consumer limits, and when SMLC's lower overhead actually matters.
Weigh container choice against delivery guarantees, resource holding, and operational recovery for a given SLA; justify defaulting to DMLC org-wide.
## The two containers A **message listener container** is the object that opens JMS sessions/consumers, receives messages, and dispatches them to your `@JmsListener` method. Spring ships two concrete implementations. ### DefaultMessageListenerContainer (DMLC) Extends `AbstractPollingMessageListenerContainer`. Each consumer thread runs a **loop**: `consumer.receive(timeout)` → if a message arrives, dispatch to the listener; otherwise loop again after the `receiveTimeout` (default 1s). This polling model is what unlocks its capabilities: - **Transactions:** because each receive+process cycle is under the container's control, DMLC can wrap it in a **local JMS transaction** (`sessionTransacted=true`) or delegate to an **external `PlatformTransactionManager`** — including `JtaTransactionManager` for XA transactions spanning JMS + a database. This is the big one: **only DMLC supports an external transaction manager.** - **Recovery:** if the broker or connection dies, DMLC catches the exception, backs off, and re-establishes the session/consumer (`recoveryInterval`, default 5s). It self-heals. - **Dynamic scaling:** `setConcurrency("3-10")` (or `setConcurrentConsumers(3)` + `setMaxConcurrentConsumers(10)`) starts 3 consumers and can grow to 10 under load, then shrink. Governed by `idleConsumerLimit`, `maxMessagesPerTask`, `idleTaskExecutionLimit`. - **Cost:** the polling loop means a thread may briefly block on `receive` and there is a small latency floor equal to loop scheduling; also each consumer holds a session. ### SimpleMessageListenerContainer (SMLC) Extends `AbstractMessageListenerContainer`. It creates a fixed set of `MessageConsumer`s and registers a JMS `MessageListener` on each — delivery is **pushed** by the JMS provider's own threads/async dispatch. Consequences: - **Fixed concurrency:** `setConcurrentConsumers(n)` fixes the count. A range like `"3-10"` is **not** honored as dynamic scaling — SMLC only reads the lower value; there is no `maxConcurrentConsumers`. - **No external transaction manager:** it supports only **local session transactions** (`sessionTransacted=true`). You **cannot** plug in `JtaTransactionManager` / XA. - **Weaker recovery:** it does not have DMLC's re-establishment loop; connection recovery depends more on the provider/`ConnectionFactory`. - **Lower overhead / no poll gap:** no receive-timeout loop, so slightly lower latency and CPU for steady simple workloads. ## Choosing - **Default to DMLC.** You want transactions, redelivery-on-exception, broker-failure recovery, and elastic consumers — all standard production needs. `DefaultJmsListenerContainerFactory` and Spring Boot produce DMLC. - **SMLC** only when: no transactions needed, fixed load, minimal overhead desired, and you accept weaker recovery. Rare in practice. ## Configuration entry points You usually don't `new` these; you configure a `JmsListenerContainerFactory`: - `DefaultJmsListenerContainerFactory` → DMLC (supports `setTransactionManager`, `setConcurrency("l-u")`, `setSessionTransacted`). - `SimpleJmsListenerContainerFactory` → SMLC. ## Gotchas - Setting `concurrency="3-10"` on an SMLC-based factory silently gives you 3 fixed consumers — no scaling. - Expecting XA/JTA to work with SMLC → it won't; you must use DMLC. - DMLC's `receiveTimeout` interacts with transaction timeouts; a very long `receiveTimeout` under a transactional consumer can hold resources.
- You set concurrency to "3-10" but consumers never scale beyond 3. What's a likely cause?The container is a SimpleMessageListenerContainer, which ignores the upper bound and runs a fixed count. Dynamic scaling requires a DefaultMessageListenerContainer.
- Why can DMLC support an external transaction manager while SMLC cannot?DMLC drives message reception itself in a receive()-then-process loop, so it can start/commit a transaction around each cycle. SMLC hands control to the provider's async push, so there's no place to bracket an external transaction.
saying these in an interview costs you the question
- Claiming SMLC supports XA/JtaTransactionManager.
- Thinking both containers scale dynamically with a concurrency range.
- Assuming SMLC recovers from broker failures like DMLC does.