A Java deserialization vulnerability arises when an application reconstructs Java objects from untrusted input using native serialization, allowing an attacker to supply a crafted byte stream that triggers arbitrary code execution during the deserialization process itself. This is not a theoretical risk. Deserialization of untrusted data (CWE-502) has driven some of the most serious Java security incidents, and it is dangerous precisely because the damage happens before your application logic ever runs. This guide explains the mechanism conceptually and focuses on the defenses that hold up.
Why deserialization is different from parsing
Java's native serialization turns an object graph into a byte stream (ObjectOutputStream) and reconstructs it on the other side (ObjectInputStream.readObject). Reconstructing an object is not a passive copy. During deserialization the runtime invokes methods on the classes being instantiated, including special hooks like readObject, readResolve, and readExternal. Those methods run automatically, with whatever data the byte stream carries.
That is the core of the problem. Parsing JSON into a data structure is comparatively inert; native Java deserialization executes behavior as a side effect of reconstruction. If an attacker controls the byte stream and the classpath contains classes whose deserialization behavior can be chained together into something useful, they can steer that automatic execution toward a payload of their choosing.
Gadget chains, conceptually
The mechanism attackers use is called a gadget chain. A "gadget" is an existing class already on the application's classpath whose methods do something interesting when invoked during deserialization. No single gadget is a vulnerability by itself. The attack works by composing a sequence of these methods so that, as the object graph is reconstructed, one gadget's behavior feeds the next until the chain culminates in an effect like executing a system command.
This is why the risk depends heavily on what libraries are present. Certain widely used libraries historically contained classes usable as gadgets, and public research tooling catalogued reusable chains against those libraries. The application code itself may never call the dangerous methods directly; the vulnerability lives in the combination of an untrusted readObject and a classpath rich in gadget material. Because the payload is a byte stream rather than source, this often bypasses input filters that were written with text injection in mind.
Where the untrusted data enters
The prerequisite for exploitation is that untrusted bytes reach a deserialization call. Common entry points include:
- HTTP request bodies, cookies, or headers containing serialized objects, sometimes base64-encoded.
- Message queue payloads or RMI/JMX traffic that carry serialized Java objects.
- Cached or session data deserialized from an external store an attacker can influence.
- File uploads or import features that accept serialized data.
The illustrative sink looks innocent:
// Dangerous: deserializing bytes from an untrusted source
ObjectInputStream in = new ObjectInputStream(request.getInputStream());
Object obj = in.readObject();
There is no working exploit here to reproduce, and that is deliberate. The point is that the vulnerability is structural: the moment readObject runs on attacker-controlled bytes, the defensive battle is largely already lost. The right place to intervene is before that line.
Defenses that actually work
The strongest defense is the simplest to state: do not deserialize untrusted data with native Java serialization. When you have a real choice, prefer a data format that does not execute behavior on reconstruction, such as JSON with an explicit, allowlisted object mapping. Treat the incoming document as data, bind it to known types, and validate.
When you cannot avoid native serialization, layer these controls:
- Allowlist filtering. Java's serialization filter (
ObjectInputFilter, available since Java 9 and backported) lets you restrict which classes may be deserialized. An allowlist of expected classes blocks the arbitrary gadget classes a chain depends on. Configure it viajdk.serialFilteror a programmatic filter on the stream.
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"com.example.model.*;java.util.*;!*");
ObjectInputStream in = new ObjectInputStream(stream);
in.setObjectInputFilter(filter);
- Least privilege on the classpath. The fewer gadget-capable libraries present, the less material an attacker has. Trim unused dependencies.
- Integrity on serialized data at rest. If you must persist serialized objects, sign them so tampered streams are rejected before deserialization.
- Network segmentation. Restrict RMI/JMX and internal serialization endpoints so they are not reachable from untrusted networks.
Keeping vulnerable gadget libraries out
Because gadget chains depend on specific library versions, dependency hygiene is a frontline defense against the Java deserialization vulnerability class. Libraries that were once shown to enable chains get fixed, but only if you actually upgrade to the patched versions, and gadget material can hide several layers deep in your transitive tree where a manual dependency review will not catch it.
This is exactly the kind of risk an SCA tool surfaces, flagging known-vulnerable versions of libraries associated with deserialization gadgets even when they are pulled in transitively. Pair that with runtime allowlist filtering and the format-level choice to avoid native serialization, and you close the gap from both directions. For a concrete example of a library with a deserialization history worth watching, see our Jackson databind security guide.
FAQ
What is a Java deserialization vulnerability?
It is a flaw where an application reconstructs Java objects from untrusted input using native serialization, letting an attacker supply a crafted byte stream that triggers code execution during deserialization. It maps to CWE-502, deserialization of untrusted data.
Why is deserialization so dangerous?
Reconstructing a Java object automatically invokes methods like readObject on the classes involved. Attackers chain existing classpath classes (gadgets) so that this automatic execution culminates in something like a system command, all before your application logic runs.
How do I prevent insecure deserialization?
Avoid native Java serialization of untrusted data; prefer JSON with explicit, allowlisted type binding. Where you must deserialize, use ObjectInputFilter to allowlist expected classes, trim unused dependencies, sign serialized data at rest, and segment internal serialization endpoints.
Can a scanner detect deserialization risk?
Static analysis can flag risky readObject sinks, and software composition analysis can flag known-vulnerable versions of libraries associated with gadget chains, including transitive ones. Combine that with runtime allowlist filtering for defense in depth.