Safeguard
AppSec

Serialization vs. Deserialization in Java: Security Implications

The difference between serialization and deserialization in Java is simple to state and dangerous to get wrong — deserialization of untrusted data has caused some of the highest-severity Java CVEs of the last decade.

Safeguard Research Team
Research
6 min read

The difference between serialization and deserialization in Java is direction: serialization converts a live object into a byte stream for storage or transmission, while deserialization reconstructs that byte stream back into a live object — and it's deserialization, not serialization, that carries the real security risk. Serializing an object is generally safe because you control the data going out. Deserializing is dangerous the moment the byte stream comes from somewhere you don't fully trust, because Java's deserialization process can be manipulated into constructing objects and invoking methods the original developer never intended. This gap has produced some of the most severe Java CVEs on record, including remote code execution bugs in mainstream frameworks. This post explains both operations, why deserialization is risky, and how to defend against it.

What is serialization in Java?

Serialization in Java is the process of converting an object's state into a sequence of bytes so it can be saved to disk, sent over a network, or cached, then reconstructed later. A class implements the marker interface java.io.Serializable, and the JVM's default mechanism (via ObjectOutputStream.writeObject()) walks the object's fields recursively, writing out enough information — field values, class metadata, references to nested objects — to rebuild an equivalent object later. Serialization by itself isn't the security problem: you're encoding data you already have and (usually) already trust, whether that's an HTTP session object being cached, a message being queued, or application state being checkpointed. The risk profile changes entirely once that byte stream leaves your control and can be modified before it comes back.

What is deserialization in Java, and why is it risky?

Deserialization in Java is the reverse process — taking a byte stream and reconstructing it into a live Java object via ObjectInputStream.readObject() — and it's risky because that reconstruction executes code as a side effect, before your application logic ever gets a chance to validate the result. When readObject() runs, it doesn't just copy bytes into fields; it invokes constructors, custom readObject() methods defined on the class, and any code that runs during object initialization. If an attacker can control the serialized byte stream, they can craft input that, when deserialized, chains together methods from classes already present on your classpath — a technique known as a "gadget chain" — to achieve outcomes like arbitrary file writes, denial of service, or full remote code execution, all without needing a memory-corruption bug. This is why OWASP lists insecure deserialization as its own risk category: the vulnerability isn't a coding mistake in your business logic, it's an inherent property of deserializing data you don't fully control.

What real-world Java deserialization vulnerabilities have there been?

Java deserialization has produced some of the highest-impact vulnerabilities in the ecosystem's history, largely through gadget chains built from common libraries already sitting on classpaths. The Apache Commons Collections gadget chain, disclosed in 2015, showed that a library present in an enormous number of Java applications could be chained through readObject() to achieve remote code execution in any application that deserialized attacker-controlled data — even though Commons Collections itself had no "vulnerability" in the traditional sense; it just provided reusable building blocks an attacker could assemble. That research directly fed into real-world RCE exploits against WebLogic, WebSphere, JBoss, Jenkins, and OpenNMS, several of which received critical CVEs and were exploited in the wild. More recently, the widely publicized Log4Shell vulnerability (CVE-2021-44228) is a related-but-distinct class of Java object/lookup-injection issue, and it renewed industry-wide attention on how much implicit trust Java frameworks place in data that ends up flowing through reflection-based mechanisms, deserialization included.

How do you prevent insecure deserialization in Java?

You prevent insecure deserialization in Java primarily by not deserializing untrusted data at all, and where you must, by strictly limiting what classes can be reconstructed. The strongest fix is architectural: avoid Java's native serialization for any data that crosses a trust boundary, and use a safer data format instead — JSON via a library that doesn't invoke arbitrary constructors, or Protocol Buffers, which define an explicit schema and don't grant an attacker control over which classes get instantiated. Where native Java deserialization is unavoidable, implement an allowlist using ObjectInputFilter (available since Java 9, backported to Java 8u121) so the deserialization process rejects any class not explicitly permitted, closing off gadget chains built from unexpected classes. Keep dependency versions current, since some gadget-chain classes have been patched specifically to stop being usable in these chains, and use static and dynamic analysis to flag readObject(), readUnshared(), and third-party deserialization libraries (like XStream or Jackson misconfigured to enable polymorphic typing) that accept attacker-influenced input.

FAQ

What's the simplest way to explain serialization vs. deserialization in Java?

Serialization turns a live object into bytes to store or send it; deserialization turns those bytes back into a live object. Serialization is an output operation you control; deserialization is an input operation that can be manipulated if the bytes come from an untrusted source.

Is Java serialization itself insecure?

Serializing data you generate and control isn't inherently insecure. The risk is concentrated entirely in deserialization, specifically when the byte stream being deserialized could have been crafted or modified by an attacker.

What is a gadget chain?

A gadget chain is a sequence of method calls, built entirely from classes already present on an application's classpath, that an attacker assembles inside a malicious serialized object so that deserializing it triggers unintended behavior — often leading to remote code execution — without exploiting a traditional memory-corruption bug.

Does using JSON instead of native Java serialization eliminate the risk?

It significantly reduces it, but misconfigured JSON libraries that enable polymorphic type handling (deserializing based on a type name embedded in the JSON) can reintroduce a similar class of gadget-chain risk, so the library configuration matters as much as the format choice.

How Safeguard Helps

Safeguard's SAST scanning flags unsafe deserialization patterns directly in Java source — including native readObject() calls on attacker-reachable input and misconfigured polymorphic deserialization in libraries like Jackson or XStream — and Safeguard's SCA engine tracks whether known gadget-chain-enabling versions of libraries like Commons Collections are present in your dependency tree, so you know if the ingredients for an exploit chain exist in your application even before a specific CVE names them.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.