Object Graph Serialization in Java
Java serialization can serialize a reachable network of objects rather than treating each object independently. During traversal, it records object identity and references so repeated references can be reconstructed as the same object after deserialization.
When an object is written with `ObjectOutputStream`, Java follows fields that lead to serializable objects and records the resulting reachable object graph. A class generally participates by implementing `Serializable`; otherwise a non-transient reference to a non-serializable object can cause `NotSerializableException`.
The serialization mechanism tracks objects it has already encountered. If two fields refer to the same object, the stream records that relationship instead of creating two independent serialized copies. Deserialization can therefore restore aliasing, where both fields point to one reconstructed instance.
Fields marked `transient` are not serialized by default, and `static` fields belong to the class rather than an individual serialized object, so they are not part of the normal per-instance serialized state. Custom `writeObject` and `readObject` methods can change how state is written and restored.
The serialized representation can also depend on `serialVersionUID`. During deserialization, Java checks the class's serialization identity and can reject incompatible versions with `InvalidClassException`. Serialization is therefore more than converting one object's fields into bytes: it captures object state plus relationships among reachable serialized objects.
Key points
- Serialization can traverse a reachable object graph.
- Object identity and repeated references can be preserved during serialization.
- `transient` instance fields are skipped by default; `static` fields are not serialized as instance state.
- A referenced object that is not serializable can cause `NotSerializableException`.
- `serialVersionUID` participates in compatibility checks during deserialization.
Interview questions
What does Java serialization mean by serializing an object graph?
It means serialization is not limited to the root object's immediate fields. When an object is written, Java can follow its serializable references and serialize the reachable objects as part of the same stream. The stream records relationships between those objects. Deserialization can reconstruct those relationships rather than treating every occurrence as an unrelated object.
If two fields point to the same object before serialization, will they point to the same object after deserialization?
Yes, provided both references are within the serialized graph and the object is reconstructed successfully. Java's serialization mechanism tracks objects it has already written and can encode subsequent references to that object. During deserialization, those references can resolve to the same reconstructed instance. This preserves aliasing within the graph.
What happens when a serializable object contains a reference to a non-serializable object?
If that reference is part of the state being serialized, Java normally cannot serialize the graph and throws `NotSerializableException`. Marking the reference `transient` excludes it from default serialization. Alternatively, custom serialization methods can explicitly control how the state is represented. The correct solution depends on whether that referenced state is required for reconstruction.
Why is `serialVersionUID` important?
`serialVersionUID` identifies the intended serialization compatibility version of a serializable class. During deserialization, the stream's class identifier is compared with the local class's serialization identifier. A mismatch can result in `InvalidClassException`. Explicitly declaring the value makes compatibility intent more predictable than relying on a compiler-generated identifier.
How does Java serialization preserve cycles in an object graph?
The serialization mechanism maintains a table of objects it has already encountered while traversing the graph. When it encounters an already-known object, it can write a reference to that existing serialized object instead of recursively serializing it again. This prevents infinite traversal for cycles. Deserialization uses those references to rebuild the cycle.