Serialize vs deserialize is simple as a definition — serializing turns a live object into a stream of bytes, and deserializing turns those bytes back into a live object — but the security difference between the two directions is enormous, because deserializing data you do not control has produced some of the most severe remote-code-execution vulnerabilities ever found. Serializing is almost always safe; you are just encoding your own trusted object. Deserializing is where the danger lives, and understanding that asymmetry is the whole point of comparing the two.
If you have searched "serialize vs deserialize java" or "serialize and deserialize in java," you were probably reaching for the mechanical definitions. The definitions take one paragraph. The security implications take the rest of this article, and they are the part worth your time.
The mechanical comparison
Serialization converts an in-memory object into a portable representation — a byte stream, a JSON string, an XML document — so it can be written to disk, sent over a network, or cached. Deserialization reverses that: it reads the representation and reconstructs the object in memory.
In Java, the classic mechanism is the Serializable interface. To serialize in Java, you write an object to an ObjectOutputStream:
// Serialize: object -> bytes (safe; you control the object)
try (ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
out.writeObject(myOrder);
}
To deserialize in Java, you read it back through an ObjectInputStream:
// Deserialize: bytes -> object (dangerous if the bytes are untrusted)
try (ObjectInputStream in = new ObjectInputStream(fileIn)) {
Order order = (Order) in.readObject();
}
Symmetric on the surface. Radically different in risk.
Why deserialize is the dangerous direction
When you serialize, you are encoding an object you already have and trust. Nothing an attacker controls is involved. When you deserialize, you are taking bytes — possibly from a network socket, an HTTP request, a message queue — and instructing the runtime to construct objects from them. The problem is what happens during construction.
Java's readObject() doesn't just copy fields into a plain struct. It runs code: custom readObject methods on the classes being reconstructed, and side effects triggered as the object graph is rebuilt. An attacker who can supply the byte stream can craft a graph that, purely by being deserialized, invokes methods that lead to command execution. This is insecure deserialization, and it is on the OWASP list of top application security risks for a reason.
The attacker never needs the class to be one of yours. They only need dangerous "gadget" classes to be present on your classpath — and popular libraries have plenty.
The gadget-chain problem, concretely
The canonical example is Apache Commons Collections. CVE-2015-7501 (tracked as GHSA-fjq5-5j5f-mvxh) covers deserialization of untrusted data in Apache Commons Collections: the library contained classes, notably around InvokerTransformer and the transformer chain, that could be composed into a "gadget chain." When a maliciously crafted object graph using that chain was deserialized, readObject() execution flowed through the chained transformers to arbitrary code execution.
The devastating part was the blast radius. Because Commons Collections was a near-ubiquitous dependency, the same technique produced zero-days against IBM WebSphere, Oracle WebLogic, and many other enterprise products in 2015. The vulnerability was not really in any one application's code — it was in the combination of "an app deserializes untrusted data" plus "a gadget library is on the classpath." Both conditions were common, which is why this class of bug spread so widely.
The lesson generalizes past that one CVE: any time your application deserializes attacker-influenced bytes with native Java serialization, the security of the operation depends on every class on your classpath, including transitive dependencies you have never heard of. A software composition analysis pass can flag when a known gadget-bearing library like a vulnerable Commons Collections version is present transitively, which is exactly the invisible condition that makes deserialization exploitable.
How to deserialize safely
The defenses, in rough order of preference:
1. Do not deserialize untrusted data with native Java serialization. This is the real fix. If the data crosses a trust boundary, do not use ObjectInputStream on it at all. Use a data-only format — JSON or Protocol Buffers — parsed into known types. A JSON parser building a declared Order object cannot be tricked into invoking arbitrary methods the way readObject() can.
// Prefer: parse untrusted input into an explicit, known type.
Order order = objectMapper.readValue(untrustedJson, Order.class);
Note that even here you must be careful: JSON libraries with polymorphic type handling (for instance Jackson's default typing) have had their own deserialization vulnerabilities. Disable polymorphic/default typing unless you genuinely need it and can lock down the allowed types.
2. If you must use Java serialization, filter classes. Modern Java (since Java 9, backported to 8u121+) provides ObjectInputFilter, letting you allowlist the exact classes permitted to be deserialized and reject everything else:
ObjectInputFilter filter =
ObjectInputFilter.Config.createFilter("com.example.Order;!*");
ois.setObjectInputFilter(filter);
An allowlist that permits only your own data classes shuts down gadget chains, because the gadget classes are never allowed to instantiate.
3. Keep dependencies patched and minimal. Every library on the classpath is a potential gadget source. Fewer dependencies and current versions reduce the pool an attacker can draw from.
The summary comparison
Serialize vs deserialize comes down to this: serializing your own object is a benign encoding step, while deserializing untrusted bytes hands an attacker a lever on your runtime. Treat the two directions with very different suspicion. Encode freely; decode defensively. When the bytes might be attacker-controlled, reach for a data-only format and explicit types rather than native Java serialization, filter classes if you truly cannot avoid ObjectInputStream, and keep an eye on the gadget libraries hiding in your dependency tree.
FAQ
What is the difference between serialize and deserialize?
Serializing converts an in-memory object into a byte stream or text representation for storage or transmission. Deserializing reverses it, reconstructing a live object from that representation. Serializing is generally safe; deserializing untrusted data is the security-sensitive direction.
Why is deserialize in Java dangerous?
Java's readObject() executes code during object reconstruction — including custom readObject methods and side effects across the object graph. An attacker who controls the byte stream can craft a "gadget chain" using classes already on your classpath to achieve remote code execution, as in CVE-2015-7501 involving Apache Commons Collections.
How do I safely serialize and deserialize in Java?
Avoid native Java serialization for untrusted data. Use a data-only format like JSON or Protocol Buffers parsed into explicit, known types, and disable polymorphic/default typing in JSON libraries. If you must use ObjectInputStream, apply an ObjectInputFilter allowlist restricting which classes can be deserialized.
Is serializing an object also a security risk?
Rarely. Serializing encodes an object you already hold and control, so no attacker input is involved. The risk concentrates almost entirely on the deserialization side, where untrusted bytes are turned back into live objects.