You open the broker’s JMX view and the TemporaryQueues attribute lists hundreds or thousands of entries. The count only goes up. Nothing in the application logs looks wrong, request-reply calls mostly work, but the destination count climbs with traffic and never comes back down. That is a temporary destination leak.
Temporary destinations back the classic JMS request-reply pattern: a client creates a TemporaryQueue, sends a request with the temp queue as the reply-to, and waits for the response. In a healthy system these destinations cycle constantly: created, used, deleted. When the count grows monotonically with request volume, something in that lifecycle is broken, and the broker is accumulating MBeans, metadata, and advisory traffic for destinations that will never be used again.
The leak is slow. It will not take the broker down today. But every leaked temp destination creates JMX MBeans and consumes heap, and left alone it feeds the destination-explosion failure pattern: growing heap, increasing GC frequency, sluggish JMX, and eventually an OOM or GC death spiral. It also interacts badly with Network of Brokers topologies, where leaked temp destinations generate advisory churn across bridges.
What this means
A temporary destination in ActiveMQ Classic is scoped to the JMS Connection that created it. Only that connection can consume from it, and critically, it is only auto-deleted when the creating connection closes. There is no idle timeout, no TTL, no broker-side garbage collection of temp destinations while their connection lives.
This single rule explains almost every leak you will see:
flowchart TD
A[Client creates TemporaryQueue] --> B[Sends request, waits for reply]
B --> C{Cleanup path}
C -->|tempQueue.delete or connection.close| D[Destination removed from broker]
C -->|connection pooled and returned| E[Temp destination stays registered]
C -->|consumer dies before reply arrives| E
C -->|delete never called| E
E --> F[Monotonic growth in TemporaryQueues count]
F --> G[MBean and heap growth, advisory churn]So the diagnostic question is never “why doesn’t the broker clean these up”. The broker is behaving exactly as specified. The question is: which client connections are staying open while their temp destinations accumulate?
Common causes
| Cause | What it looks like | First thing to check |
|---|---|---|
| Connection pooling holds creating connections open | Temp count grows steadily with request volume; connections rarely close | Is the client using PooledConnectionFactory or a JCA pool? |
| Client never calls TemporaryQueue.delete() | Growth proportional to request-reply calls; one temp queue per request | Read the request-reply code path: is there a per-request createTemporaryQueue with no matching delete? |
| Reply consumer dies before consuming the response | Leaked destinations correlate with client errors or timeouts | Client-side logs for timeouts, exceptions, or threads killed mid-request |
| Per-request temp queue antipattern | Huge churn: creation rate equals request rate | Recommended pattern is one temp queue per client, reused, with JMSCorrelationID matching |
| Network of Brokers interaction | “Temp destination does not exist” errors on remote brokers after reconnection; temp-related subscriptions lingering on bridges | Bridge logs and remote broker errors after network blips |
The first two causes dominate. Connection pooling is the subtle one: the pool keeps the underlying JMS Connection alive between logical borrows, so the broker-side rule “delete temp destinations when the connection closes” never fires. From the broker’s perspective the connection lives for days, and every temp destination it ever created stays registered.
Quick checks
All read-only. Run against the web console’s Jolokia endpoint (default port 8161) or any JMX client.
# Count active temporary queues
curl -s -u admin:admin \
'http://localhost:8161/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=localhost/TemporaryQueues' \
| python3 -c "import json,sys; print(len(json.load(sys.stdin)['value']))"
# Count active temporary topics
curl -s -u admin:admin \
'http://localhost:8161/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=localhost/TemporaryTopics' \
| python3 -c "import json,sys; print(len(json.load(sys.stdin)['value']))"
# Watch the growth rate: sample twice, 5 minutes apart
for i in 1 2; do
curl -s -u admin:admin \
'http://localhost:8161/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=localhost/TemporaryQueues' \
| python3 -c "import json,sys; print(len(json.load(sys.stdin)['value']))"
sleep 300
done
# Compare with connection count: leak with stable connections points at pooling
curl -s -u admin:admin \
'http://localhost:8161/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=localhost/CurrentConnectionsCount'
# Total destination count: temp leaks feed destination explosion
curl -s -u admin:admin \
'http://localhost:8161/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=localhost/Queues' \
| python3 -c "import json,sys; print(len(json.load(sys.stdin)['value']))"
# JVM heap: is the leak already pressuring the broker?
curl -s -u admin:admin \
'http://localhost:8161/api/jolokia/read/java.lang:type=Memory/HeapMemoryUsage'
Interpretation of the two-sample check: a healthy request-reply system oscillates. The count goes up during bursts and back down as requests complete and connections cycle. A count that ratchets upward and never returns to baseline is a leak, regardless of the absolute number.
How to diagnose it
Confirm the growth pattern. Sample TemporaryQueues and TemporaryTopics over at least 15 to 30 minutes, alongside application request volume. If temp count tracks request count with no decay, you have a leak, not a burst.
Correlate with connection count. Pull CurrentConnectionsCount over the same window. If connections are stable but temp destinations grow, long-lived connections are accumulating temp destinations: connection pooling or a long-lived client creating per-request temp queues. If connections also grow, you may have a connection leak as well; see ActiveMQ connection and session leak: clients that never close.
Identify the owning clients. Temp destinations are created by specific connections. Enumerate connection MBeans and their client IDs and remote addresses to find which application hosts are responsible. Thread names in a broker thread dump also carry client IPs if you need to go deeper.
Read the client code path. Look for the request-reply implementation. Red flags:
createTemporaryQueue()inside a per-request method with nodelete()in a finally block; a PooledConnectionFactory wrapping the connection that creates temp destinations; a reply consumer created per request that can be abandoned on timeout. The supported pattern is one temp queue per client created at startup, reused for all requests, with responses matched by JMSCorrelationID.Check for NoB involvement. In a Network of Brokers, temp destinations interact poorly with bridges. Look for “temp destination does not exist” errors on remote brokers after bridge reconnection, and for temp-destination advisory traffic flooding the network. Undeleted temp queues generate advisory messages on ActiveMQ.Advisory.TempQueue for every creation and deletion event, and in broker networks this advisory churn can consume significant broker memory on its own.
Rule out client-side advisory staleness. If clients report
InvalidDestinationException: Cannot publish to a deleted Destinationfor temp queues that should exist (AMQ-5250 territory), the client’s advisory-based destination tracking may be stale. Settingjms.watchTopicAdvisories=falseon the connection factory URL makes the client ask the broker whether a temp destination exists instead of trusting cached advisory state. This is also required if you disabled advisories on the broker.
Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
| TemporaryQueues / TemporaryTopics count (Broker MBean) | The leak itself, directly measured | Greater than 100, or any sustained monotonic growth |
| Rate of change of temp destination count | Distinguishes burst from leak | Positive slope over hours that never returns to baseline |
| CurrentConnectionsCount | Separates pooled-connection leaks from connection leaks | Stable connections + growing temp count = pooling or missing delete() |
| Total destination count | Temp leaks feed destination explosion and MBean growth | Count climbing without corresponding application scaling |
| JVM heap after major GC | Each destination creates at least 4 MBeans and consumes heap | Upward trend after GC correlating with destination growth |
| GC pause duration and frequency | Destination explosion ends in GC pressure | Increasing pause frequency as destination count grows |
| Advisory topic activity (ActiveMQ.Advisory.TempQueue) | Temp churn generates advisory traffic; in NoB it can flood bridges | Advisory destinations consuming meaningful memory or message volume |
Fixes
Fix the client lifecycle
The durable fix is in the client. Every createTemporaryQueue() must have a matching TemporaryQueue.delete(), ideally in a finally block so timeouts and exceptions do not skip it. Better still, stop creating temp destinations per request: create one TemporaryQueue per client at startup, reuse it for the life of the client, and correlate responses with JMSCorrelationID. This eliminates the churn entirely and is the pattern ActiveMQ’s own request-response documentation recommends. Tradeoff: correlation-id matching adds a small amount of client complexity, and a shared reply queue needs care if multiple threads consume from it.
Constrain connection pooling
If a PooledConnectionFactory or JCA pool holds connections open for days, temp destinations created on those connections live for days. Options, in increasing order of disruption: call delete() explicitly after each request so pooled connections stay clean; cap the lifetime or reuse count of pooled connections so they eventually close and take their temp destinations with them; or segregate request-reply traffic onto its own non-pooled, short-lived connections. The tradeoff is connection establishment cost, which is exactly what the pool was there to avoid.
Handle the dying reply consumer
When a reply consumer times out or its thread is killed before the response arrives, the temp destination is orphaned until the connection closes. Make sure timeout paths also run cleanup: close the consumer and delete the temp destination on the error path, not just the happy path. If the creating connection is pooled, the delete() call is mandatory here because the connection will not close.
Broker-side mitigation and cleanup
There is no broker-side auto-cleanup for temp destinations while their connection lives; gcInactiveDestinations and destination purge policies apply to regular destinations. The broker-side lever is operational: identify the offending connections via JMX and close them, which triggers the auto-delete. This drops the owning client’s sessions and in-flight work, so treat it as disruptive and coordinate with the application team before doing it. You can also reduce the blast radius by disabling advisory support on destinations that do not need it (clients must then set jms.watchTopicAdvisories=false), which cuts the advisory MBean overhead per leaked destination.
Version note
On ActiveMQ Classic 5.19.8 / 6.2.7 and later, temp destination isolation was tightened: only the creating connection can consume from a temp destination, and the previous permissive behavior (now gated behind allowTempDestinationStealing, defaulting to false) was removed as part of the fix for CVE-2026-54475, a temp-destination ownership takeover vulnerability. If you run failover or network-bridging setups that relied on a different connection consuming replies from a temp destination, those patterns break on upgrade, and you should treat that as a design problem to fix rather than a flag to re-enable permanently.
Prevention
- Adopt the reuse pattern as a standard. One temp destination per client, JMSCorrelationID matching, delete on shutdown. Per-request temp queue creation should fail code review.
- Alert on the count, not the incident. Page nothing here; ticket when TemporaryQueues or TemporaryTopics exceeds 100 or shows sustained growth. This is a slow leak and you want to catch it at hundreds, not at tens of thousands when GC starts to suffer.
- Track the trend, not just the threshold. A baseline of 40 temp destinations that climbs 10 per day will cross any static threshold eventually, and the slope tells you the leak rate before the absolute number matters.
- Watch downstream pressure signals. Total destination count, JVM heap after GC, and GC pause frequency tell you whether the leak is still cosmetic or is starting to degrade the broker.
- Test the failure paths. Timeout, exception, and deployment-restart paths are where reply consumers die and cleanup gets skipped. Exercise them deliberately.
How Netdata helps
- Netdata’s ActiveMQ collector pulls the Broker MBean via JMX, including the TemporaryQueues and TemporaryTopics attributes, so temp destination count is charted continuously rather than sampled by hand during an incident.
- Plotting temp destination count next to CurrentConnectionsCount on one dashboard makes the pooling-versus-connection-leak distinction immediate: stable connections with rising temp destinations is the pooling signature.
- Correlating temp destination growth with total destination count, JVM heap, and GC pause metrics shows whether the leak is still contained or is feeding the destination-explosion pattern, without switching tools.
- Per-second collection catches the oscillation-versus-ratchet difference in the temp count during traffic bursts, which coarse 5-minute polling tends to hide.
- Alerts on sustained growth (not just absolute threshold) surface the leak at ticket severity days before it becomes a heap or GC problem.
Related guides
- ActiveMQ broker down: telling a crashed broker from a hung one
- ActiveMQ broker won’t start: port conflicts, store recovery, and lock contention
- ActiveMQ InactivityIOException: Channel was inactive for too long
- ActiveMQ connection and session leak: clients that never close
- ActiveMQ consumers connected but not acknowledging: the zombie consumer
- ActiveMQ disk full on the KahaDB partition: write failures and store corruption risk
- ActiveMQ.DLQ growing: dead letter queue accumulation and poison messages
- ActiveMQ DLQ never expires: setting TTL so the dead-letter queue stops leaking storage
- ActiveMQ offline durable subscriber pending messages: the silent storage leak
- ActiveMQ enqueue outpacing dequeue: reading the rate imbalance before the backlog
- ActiveMQ expired message count climbing: TTL expiry and silent correctness loss
- ActiveMQ GC pause death spiral: long pauses, heartbeat timeouts, and reconnect storms






