ClassNotFoundException, InvalidClassException, or StreamCorruptedException in the ActiveMQ broker log means messages are failing to process. Sometimes this is mundane: a producer and consumer compiled against different versions of a class, or an ObjectMessage payload class that is not on the broker’s classpath. Sometimes it is the signature of a Java deserialization exploit attempt, and the correct response is a security incident, not a config tweak.
The two situations look similar in the log. The difference is in which class names appear, how often, and from where. This guide is about making that call quickly, fixing the benign cases correctly, and locking down the whitelist so the dangerous cases fail closed.
What this means
ActiveMQ’s ObjectMessage carries a serialized Java object as its payload. To deliver it, something has to deserialize the bytes: the consumer always does, and the broker can too (for example when browsing queues through the web console or applying certain selectors). Java deserialization instantiates whatever class the byte stream names, which is why it has been a reliable RCE vector: CVE-2015-5254 used Commons Collections gadget chains delivered as crafted ObjectMessages against ActiveMQ 5.0.0 through 5.12.1.
Since 5.12.2 and 5.13.0, ActiveMQ enforces an explicit package whitelist for ObjectMessage deserialization, controlled by the system property org.apache.activemq.SERIALIZABLE_PACKAGES. When a class outside the whitelist is encountered, deserialization is refused and the broker logs a ClassNotFoundException of the form Forbidden class <fully.qualified.ClassName>! This class is not trusted to be serialized as ObjectMessage payload. That refusal is the control working. Your job is to decide whether the thing being refused is your application’s own payload class (configuration problem) or a gadget class that has no business on your broker (attack).
flowchart TD
A[Deserialization error in broker log] --> B{Which class is named?}
B -->|Your application payload class| C[Benign: classpath or whitelist gap]
B -->|Gadget class: commons-collections functors, xalan TemplatesImpl, spring beans| D[Escalate as security incident]
B -->|Same app class, InvalidClassException| E[serialVersionUID / version mismatch between producer and consumer]
C --> F[Add package to SERIALIZABLE_PACKAGES or client trusted packages]
D --> G[Patch level, exposure check, source IP hunt]
E --> H[Align class versions across producer and consumer]Common causes
| Cause | What it looks like | First thing to check |
|---|---|---|
| Payload class not whitelisted | ClassNotFoundException: Forbidden class com.example.Order! This class is not trusted... after an upgrade or first deployment of ObjectMessage | Is the class in SERIALIZABLE_PACKAGES on the broker and in the client’s trusted packages? |
| ObjectMessage class absent from broker classpath | ClassNotFoundException naming an app class, but not the “Forbidden class” wording; broker fails when browsing or inspecting messages | Does the class need to be on the broker classpath at all, or only on the consumer? |
| Producer/consumer class version mismatch | InvalidClassException with serialVersionUID mismatch; started right after deploying one side but not the other | Which side was deployed last? Do both sides load the same version of the class? |
| Corrupt or tampered payload | StreamCorruptedException, often isolated single messages | Inspect the offending message in the DLQ; check producer for serialization bugs |
| Exploit probe (CVE-2015-5254 style) | Repeated ClassNotFoundException for gadget classes like org.apache.commons.collections.functors.InvokerTransformer or com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl | Broker version and patch level; source connections on OpenWire; exposure to untrusted networks |
| Whitelist set but not applied | SERIALIZABLE_PACKAGES configured, errors persist | Property actually exported into the broker JVM? Client-side factory overriding it? |
Quick checks
# Count and sample deserialization errors in the broker log
grep -c "ClassNotFoundException\|InvalidClassException\|StreamCorruptedException" /opt/activemq/data/activemq.log
grep -h "ClassNotFoundException\|InvalidClassException" /opt/activemq/data/activemq.log | tail -30
# Look specifically for known gadget-class signatures (escalate if these appear)
grep -i "commons.collections\|InvokerTransformer\|TemplatesImpl\|springframework.beans" /opt/activemq/data/activemq.log
# Confirm the whitelist property actually reached the broker JVM
BROKER_PID=$(pgrep -f activemq)
tr '\0' ' ' < /proc/$BROKER_PID/cmdline | grep -o "SERIALIZABLE_PACKAGES=[^ ]*"
# Check the broker version (patch level determines CVE exposure)
grep -m1 "Apache ActiveMQ" /opt/activemq/data/activemq.log
All read-only. Setting the property in a startup script is not enough: if it is not exported into the broker process environment or passed on the JVM command line, the broker never sees it. The /proc/<pid>/cmdline check settles that in one step.
How to diagnose it
Extract the class names. The exception always names a class. Collect every distinct class name from the errors over the last hour, not just the most recent one. A single app class points at a configuration gap. A rotating cast of framework internals points at probing.
Classify each name. Your own payload classes (your domain packages): benign, fix the whitelist or classpath. Gadget classes (
org.apache.commons.collections.functors.*, xalanTemplatesImpl, Spring beans factory internals): treat as hostile. These classes exist to build deserialization exploit chains; no legitimate producer sends them.Distinguish the exception type.
ClassNotFoundException: Forbidden class ...is the whitelist refusing a class; the control is working. PlainClassNotFoundExceptionwithout “Forbidden” means the class is trusted but missing from the classpath.InvalidClassExceptionwith aserialVersionUIDline means both sides have the class but compiled from different versions.StreamCorruptedExceptionmeans the byte stream itself is malformed: a buggy producer, a corrupted message, or something that is not a serialized Java object at all.Correlate with deployments. Check when a producer or consumer was last deployed. A
serialVersionUIDmismatch that starts minutes after a rolling deploy is your answer: the sides are on different class versions and messages produced by the new version fail on the old consumer (or vice versa).Correlate with consumer behavior. If failures are on the consumer side, messages fail processing, get redelivered up to
maximumRedeliveries(default 6), and land inActiveMQ.DLQ. Check DLQ depth and inspect message properties:JMSDestinationtells you the source queue. A growing DLQ alongside deserialization errors is the operational footprint of this failure.If gadget classes appear, escalate. Check the broker version against CVE-2015-5254 (affects 5.0.0-5.12.1) and CVE-2023-46604 (OpenWire deserialization RCE; see the related guide). Identify which transport connector and which remote IPs the attempts arrive on, and whether the OpenWire port is reachable from untrusted networks. This is no longer a tuning exercise.
Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
| Deserialization error rate in broker log | The primary detection surface for both config gaps and exploit probes | Any gadget-class names; sudden rate increase |
| DLQ depth | Deserialization-failing messages end up here after redeliveries | Growth correlated with error bursts; see ActiveMQ.DLQ growing |
| Redelivery rate | Leading indicator: messages cycling before they dead-letter | Sustained redelivery on queues carrying ObjectMessage |
| Dequeue rate on affected queues | Consumers that cannot deserialize stop making progress | Dequeue collapse while consumers stay connected |
| Connection count and accept rate | Exploit scanning often comes as new short-lived connections | New sources with immediate errors and disconnects |
| Broker version / patch age | Determines which deserialization CVEs apply | Anything in the CVE-2015-5254 or CVE-2023-46604 affected ranges |
Fixes
Add your application packages to the whitelist
Set org.apache.activemq.SERIALIZABLE_PACKAGES on the broker JVM to a comma-separated list of the packages your ObjectMessage payloads actually use, for example via ACTIVEMQ_OPTS:
# Example: whitelist only your application packages plus JDK basics your payloads need
ACTIVEMQ_OPTS="$ACTIVEMQ_OPTS -Dorg.apache.activemq.SERIALIZABLE_PACKAGES=com.example.orders,com.example.common,java.lang,java.util"
Requires a broker restart to take effect, so plan for the brief outage or failover. Keep the list tight: every package you add is attack surface if a deserialization gadget exists in it.
Clients have their own check. Configure ActiveMQConnectionFactory.setTrustedPackages(...) with the same package list so consumers can deserialize their payloads. Client-side settings override the system properties if both are set. setTrustAllPackages(true) and the wildcard -Dorg.apache.activemq.SERIALIZABLE_PACKAGES=* both disable the control entirely; acceptable briefly in a test environment, never in production.
Fix version mismatches
Align the payload class versions across producers and consumers, ideally by shipping the payload classes from a single shared artifact. If you cannot deploy both sides together, drain the affected queues before cutover, because old-format messages will keep failing against the new class. Explicitly declaring a stable serialVersionUID in your payload classes prevents the JDK from generating a new one on every recompile.
Fix classpath gaps
If the broker itself must handle the class (queue browsing through the web console is the common trigger), put the payload jar on the broker classpath. The better answer is usually to avoid ObjectMessage for anything the broker needs to inspect: use TextMessage with JSON or BytesMessage, and keep Java serialization between producer and consumer only. This also shrinks your exposure to the entire class of deserialization bugs.
Respond to exploit probes
If you found gadget-class signatures: verify the broker is patched past the CVE-2015-5254 and CVE-2023-46604 affected versions, confirm the whitelist is strict (the whitelist refusing the gadget class is what saved you; the errors are the evidence), restrict network reachability of the OpenWire connector to known producers/consumers, and review authentication logs for the source IPs. Repeated gadget-class signatures warrant a formal security review, not just a log rotation.
Prevention
- Keep the whitelist explicit and minimal. Enumerate your application’s payload packages; do not carry the wildcard past initial bring-up.
- Prefer non-ObjectMessage payloads. JSON over TextMessage removes broker-side deserialization of arbitrary classes and makes payloads language-neutral.
- Track version changes. Whitelist behavior has changed across releases; read the release notes on every upgrade.
- Alert on the signal, not the symptom. A log-pattern alert on deserialization exceptions, with gadget-class names routing to the security queue, catches both config regressions and probes.
- Watch the DLQ. Deserialization failures that slip past alerting still land there; a DLQ alert is your second net.
How Netdata helps
- Log-pattern correlation: counting
ClassNotFoundException,InvalidClassException, andStreamCorruptedExceptionin the broker log over time turns a one-off stack trace into a rate you can alert on. - DLQ and redelivery correlation: Netdata’s ActiveMQ collector surfaces DLQ depth and enqueue/dequeue rates per destination, so you can see deserialization failures converting into dead-lettered messages in the same view as the log errors.
- Consumer progress: per-destination dequeue rate and consumer count show whether consumers are making progress or silently stuck on payloads they cannot deserialize.
- Connection context: connection count and accept behavior help distinguish a misconfigured application reconnecting and retrying from external probing traffic.
Related guides
- ActiveMQ CVE-2023-46604: the OpenWire deserialization RCE and how to detect exposure
- ActiveMQ.DLQ growing: dead letter queue accumulation and poison messages
- ActiveMQ consumers connected but not acknowledging: the zombie consumer
- ActiveMQ authentication failures: credential rotation fallout and brute force
- ActiveMQ broker won’t start: port conflicts, store recovery, and lock contention






