skip to content

Serialization

How Java's native serialization works, how to customize it, and why most teams avoid it entirely. Interviewers ask about serialVersionUID and about the security problem behind the avoidance.

part ofJavaoverview, primer and where to startread it →
on this pageshow

explore

questions

19

What is the Serializable interface in Java, and what does implementing it actually do?

level: juniorimportance: must knowfreq 70%

answer

  1. Marker interface, no methods
  2. ObjectOutputStream/ObjectInputStream
  3. Whole object graph, recursive
  4. NotSerializableException at runtime
  5. serialVersionUID controls versioning

basics

~10 s

Serializable is an empty interface a class implements to say its objects can be turned into bytes and back. It has no methods; it just marks the class as allowed to be serialized.

solid answer

~40 s

Serializable is a marker interface (it declares no methods) in java.io. Implementing it tells the JVM that instances may be converted into a byte stream by ObjectOutputStream and reconstructed by ObjectInputStream. Serialization captures the object graph: the object's non-transient, non-static instance fields, recursively following references, so every referenced object must also be Serializable or you get a NotSerializableException at runtime. Because it's a marker, you write no code for default behavior; the JVM uses reflection. You should also declare a serialVersionUID so versioning is explicit. It is used to persist objects to disk, send them over a network (RMI), or cache them, though modern systems often prefer JSON/Protobuf for cross-platform safety.

code

java · 20 lines
java
import java.io.*;

class Point implements Serializable {
    private static final long serialVersionUID = 1L;
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
}

public class Demo {
    public static void main(String[] args) throws Exception {
        Point p = new Point(3, 4);
        try (var out = new ObjectOutputStream(new FileOutputStream("p.ser"))) {
            out.writeObject(p);            // serialize
        }
        try (var in = new ObjectInputStream(new FileInputStream("p.ser"))) {
            Point q = (Point) in.readObject(); // deserialize
            System.out.println(q.x + "," + q.y); // 3,4
        }
    }
}

go deeper

for a junior

Knows Serializable is an empty marker interface, that you add 'implements Serializable', and that ObjectOutputStream/ObjectInputStream do the work.

for a middle

Explains the object-graph recursion, NotSerializableException, the role of serialVersionUID, and that transient/static fields are excluded.

for a senior

Discusses versioning compatibility, when to prefer JSON/Protobuf, security risks of deserializing untrusted input, and that the constructor is bypassed.

for a principal

Frames serialization in system design: format choice for evolution and interop, deserialization attack surface and mitigations (allowlists, ObjectInputFilter), and why Java serialization is largely discouraged for new external boundaries.

## What serialization is **Serialization** is converting an in-memory Java object into a flat sequence of **bytes** so it can be stored (file, DB) or transmitted (network) and later **deserialized** — reconstructed into an equivalent object. Think of flattening a structure into a ribbon you can mail, then unfolding it on the other side. ## The Serializable interface `java.io.Serializable` is a **marker interface**: an interface with **no methods and no fields**. A marker interface exists only to *tag* a class with metadata the runtime checks. By writing `class Foo implements Serializable`, you assert "instances of Foo may be serialized." You add **no code** — the JVM provides the default behavior automatically. ## How the default mechanism works You serialize with **`ObjectOutputStream`** and its `writeObject(obj)` method, and read back with **`ObjectInputStream`** and `readObject()`. Under the hood the JVM uses **reflection** to walk the object's fields: - It writes the class descriptor (class name, `serialVersionUID`, field metadata). - It writes each **instance field** that is **not `transient`** and **not `static`**. - For each field that is itself an object reference, it recursively serializes that object too. This is the **object graph**: a tree/graph of all reachable objects. Shared references and cycles are handled by a back-reference table, so the same object isn't written twice and cycles don't loop forever. ## Why every referenced object must be Serializable If the graph reaches an object whose class does **not** implement `Serializable`, serialization fails at runtime with **`java.io.NotSerializableException`**, naming the offending class. There is no compile-time check — `Serializable` has no methods to force, so the requirement is verified only when you actually serialize. ## serialVersionUID Each serializable class has a `serialVersionUID` — a long that identifies the class version. If you don't declare one, the compiler generates it from the class structure; any structural change produces a different value, and deserializing old bytes throws `InvalidClassException`. Best practice: declare `private static final long serialVersionUID = 1L;` explicitly so you control compatibility. ## What it does NOT serialize Default serialization skips **`transient`** fields and **`static`** fields (statics belong to the class, not the instance). Constructors are **not** run on deserialization — the object is rebuilt directly from bytes via reflection. ## When to use / avoid Use it for quick JVM-to-JVM persistence, caching, or legacy RMI. Avoid it for untrusted input (deserializing attacker-controlled bytes is a major security risk — gadget-chain RCE) and for cross-language or long-term formats, where JSON, Protobuf, or Avro are safer and more portable.

  • What happens if a serializable class references an object whose class is not Serializable?
    Serialization throws java.io.NotSerializableException at runtime, naming that class. There's no compile-time check because Serializable has no methods. Fix it by making the referenced class Serializable or marking that field transient.
  • Does deserialization call the constructor?
    No. The object is reconstructed via reflection from the byte stream; no constructor of the serializable class runs. (The no-arg constructor of the first non-serializable superclass IS invoked, however.)

Serializable is like a 'this parcel may be shipped' sticker — it carries no instructions, it just authorizes the postal system (the JVM) to flatten and box the contents.

saying these in an interview costs you the question

  • Thinking Serializable has methods you must implement
  • Believing it does a compile-time check that the whole graph is serializable
  • Assuming static fields are saved
  • Thinking the constructor runs on deserialization
  • Ignoring serialVersionUID and being surprised by InvalidClassException

context

open as a page

What is serialVersionUID and why does a Serializable class have one?

level: juniorimportance: must knowfreq 60%

basics

~20 s

serialVersionUID is a version number on a Serializable class. When Java reads back a saved object, it checks that the stored number matches the class's number. If they differ, it refuses to load the object.

open as a page

What does the transient keyword do during serialization, and when would you use it?

level: middleimportance: must knowfreq 68%

basics

~10 s

transient marks an instance field to be skipped during serialization. Its value is not written out, and after deserialization it comes back as the default for its type (0, false, or null).

open as a page

What are the private writeObject and readObject methods in Java serialization, and when do you use them?

level: middleimportance: must knowfreq 62%

basics

~20 s

They are special private methods you add to a Serializable class. Java's serialization machinery calls them automatically to let you control how the object is written to and read from a stream, instead of using the default field-by-field behavior.

open as a page

Why is Java's native serialization considered dangerous when reading data from untrusted sources?

level: middleimportance: must knowfreq 72%

basics

~20 s

When Java reads a serialized object, it rebuilds objects from the bytes before you can check them. An attacker can craft bytes that, while being rebuilt, run unexpected code or cause harm, so reading untrusted serialized data is risky.

open as a page

Why is relying on the auto-generated serialVersionUID dangerous?

level: middleimportance: must knowfreq 55%

basics

~10 s

If you don't declare serialVersionUID, Java computes one from the class's structure. Almost any code change recomputes a different value, so old saved objects suddenly fail to load even when the change was harmless.

open as a page

What do readResolve and writeReplace do, and why are they essential for serializing singletons?

level: seniorimportance: must knowfreq 58%

basics

~20 s

They let a class swap one object for another during serialization. writeReplace replaces the object being written; readResolve replaces the object just produced by deserialization. For a singleton, readResolve returns the existing single instance so deserialization doesn't create a duplicate.

open as a page

Are static fields serialized by Java's default serialization? Explain why.

level: middleimportance: should knowfreq 55%

basics

~10 s

No. Default serialization saves only instance fields. Static fields belong to the class, not to any single object, so there is nothing instance-specific to write, and they aren't included in the byte stream.

open as a page

How do you correctly persist a field marked transient using the writeObject/readObject hooks?

level: middleimportance: should knowfreq 42%

basics

~10 s

Mark the field transient so default serialization skips it, then in writeObject call defaultWriteObject() and manually write the field; in readObject call defaultReadObject() and manually read it back in the same order.

open as a page

Why are data formats like JSON or Protobuf preferred over Java native serialization, and what must you still watch for?

level: middleimportance: should knowfreq 55%

basics

~20 s

JSON and Protobuf describe plain data, not Java objects, so reading them doesn't automatically run code or rebuild arbitrary classes. They're also cross-language and more stable across versions. You still must avoid letting a library auto-create arbitrary types from the input.

open as a page

How should serialVersionUID be declared, and why those specific modifiers?

level: middleimportance: should knowfreq 35%

basics

~20 s

Declare it as private static final long serialVersionUID = 1L;. It's static because it belongs to the class, final because it never changes at runtime, long because that's the required type, and private by convention.

open as a page

What is serialVersionUID, and what goes wrong if you don't declare it?

level: seniorimportance: should knowfreq 58%

basics

~20 s

serialVersionUID is a version number for a serializable class. If you don't declare one, the compiler generates it from the class structure; any change creates a new value, so old saved bytes can no longer be read and you get InvalidClassException.

open as a page

How does Externalizable differ from implementing Serializable with writeObject/readObject?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Externalizable is a stronger interface where YOU write all the serialization code by hand in writeExternal/readExternal — there is no automatic field handling at all. Serializable with writeObject/readObject still does the default work for you unless you opt out.

open as a page

How does serialization traverse an inheritance hierarchy when a superclass is not Serializable?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Only the fields of Serializable classes in the hierarchy are saved. A non-Serializable superclass's fields are skipped on write, and on read that superclass is rebuilt by calling its no-arg constructor. If it has no accessible no-arg constructor, deserialization fails.

open as a page

How do Java object input filters (JEP 290) mitigate deserialization attacks, and what are their limits?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Java lets you install a filter that checks each class in an incoming serialized stream before it is rebuilt, and you can reject any class not on an allowlist. It also caps things like depth and array size to stop resource-exhaustion attacks.

open as a page

With an explicit serialVersionUID, which class changes stay backward-compatible and which break deserialization?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Keeping the same serialVersionUID tells Java to try matching old bytes to the new class. Adding fields or removing fields usually still works (new fields get defaults, missing ones are ignored). Changing a field's type or making the class no longer Serializable breaks it.

open as a page

Why is deserializing untrusted data with Java's built-in serialization dangerous, and what mitigations exist?

level: principalimportance: should knowfreq 45%

basics

~20 s

Reading objects from untrusted bytes lets an attacker control what classes get instantiated and which methods run during deserialization, which can lead to remote code execution. Don't deserialize untrusted input; prefer safe formats like JSON, or filter allowed classes.

open as a page

You inherit a service that deserializes Java objects from a message queue. Lay out a strategy to make it safe.

level: principalimportance: should knowfreq 33%

basics

~20 s

First confirm whether the input can be untrusted. The best fix is to stop using Java native serialization and switch to a data format like JSON or Protobuf. Until then, add a strict allowlist filter and size limits, patch libraries, and shrink the classpath.

open as a page

How would you manage serialVersionUID and serialized-form evolution as a policy across a large, long-lived system?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Decide up front where Java serialization is even allowed. Where it is, require every Serializable class to declare an explicit serialVersionUID, keep it stable for compatible changes, and prefer a schema'd format (JSON/Protobuf) for anything stored long-term or sent between services.

open as a page