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

CauseWhat it looks likeFirst thing to check
Payload class not whitelistedClassNotFoundException: Forbidden class com.example.Order! This class is not trusted... after an upgrade or first deployment of ObjectMessageIs the class in SERIALIZABLE_PACKAGES on the broker and in the client’s trusted packages?
ObjectMessage class absent from broker classpathClassNotFoundException naming an app class, but not the “Forbidden class” wording; broker fails when browsing or inspecting messagesDoes the class need to be on the broker classpath at all, or only on the consumer?
Producer/consumer class version mismatchInvalidClassException with serialVersionUID mismatch; started right after deploying one side but not the otherWhich side was deployed last? Do both sides load the same version of the class?
Corrupt or tampered payloadStreamCorruptedException, often isolated single messagesInspect 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.TemplatesImplBroker version and patch level; source connections on OpenWire; exposure to untrusted networks
Whitelist set but not appliedSERIALIZABLE_PACKAGES configured, errors persistProperty 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

  1. 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.

  2. Classify each name. Your own payload classes (your domain packages): benign, fix the whitelist or classpath. Gadget classes (org.apache.commons.collections.functors.*, xalan TemplatesImpl, Spring beans factory internals): treat as hostile. These classes exist to build deserialization exploit chains; no legitimate producer sends them.

  3. Distinguish the exception type. ClassNotFoundException: Forbidden class ... is the whitelist refusing a class; the control is working. Plain ClassNotFoundException without “Forbidden” means the class is trusted but missing from the classpath. InvalidClassException with a serialVersionUID line means both sides have the class but compiled from different versions. StreamCorruptedException means the byte stream itself is malformed: a buggy producer, a corrupted message, or something that is not a serialized Java object at all.

  4. Correlate with deployments. Check when a producer or consumer was last deployed. A serialVersionUID mismatch 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).

  5. Correlate with consumer behavior. If failures are on the consumer side, messages fail processing, get redelivered up to maximumRedeliveries (default 6), and land in ActiveMQ.DLQ. Check DLQ depth and inspect message properties: JMSDestination tells you the source queue. A growing DLQ alongside deserialization errors is the operational footprint of this failure.

  6. 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

SignalWhy it mattersWarning sign
Deserialization error rate in broker logThe primary detection surface for both config gaps and exploit probesAny gadget-class names; sudden rate increase
DLQ depthDeserialization-failing messages end up here after redeliveriesGrowth correlated with error bursts; see ActiveMQ.DLQ growing
Redelivery rateLeading indicator: messages cycling before they dead-letterSustained redelivery on queues carrying ObjectMessage
Dequeue rate on affected queuesConsumers that cannot deserialize stop making progressDequeue collapse while consumers stay connected
Connection count and accept rateExploit scanning often comes as new short-lived connectionsNew sources with immediate errors and disconnects
Broker version / patch ageDetermines which deserialization CVEs applyAnything 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, and StreamCorruptedException in 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.