Why is the state of an inherited (non-serializable parent) field lost after a serialize/deserialize round-trip, and how can you preserve it?
answer
- Parent fields skipped -> rebuilt by no-arg ctor -> defaults
- Preserve via subclass writeObject/readObject
- defaultWriteObject/defaultReadObject + manual parent field I/O
- Only works if parent fields are accessible (not private)
- Brittle: must hand-maintain when parent changes
basics
~20 sThe non-serializable parent's fields are never written to the stream, so on the way back Java rebuilds the parent by calling its no-arg constructor, which resets those fields. To keep them, the serializable subclass can manually write and restore the parent's values using custom writeObject/readObject methods.
solid answer
~40 sSerialization only captures fields of classes in the hierarchy that are themselves Serializable. A non-serializable superclass's fields are not written, so during deserialization Java reconstructs that part by invoking the superclass's no-arg constructor; the inherited fields therefore hold whatever that constructor sets, not their pre-serialization values. To preserve them, give the serializable subclass custom `private void writeObject(ObjectOutputStream)` and `private void readObject(ObjectInputStream)` methods. In writeObject, call `defaultWriteObject()` then explicitly write the accessible parent fields; in readObject, call `defaultReadObject()` then read them back and set them (via accessible setters or protected/package fields). This works only if the subclass can actually read and write those parent fields. If the parent fields are private with no accessors, you cannot recover them this way and should reconsider the design.
go deeper
Recognizes that fields from a non-serializable parent are not saved and come back as defaults after deserialization.
Explains the mechanism (parent not in stream, rebuilt by no-arg constructor) and knows the lost-state symptom.
Implements writeObject/readObject to preserve accessible parent fields, calls defaultWriteObject/defaultReadObject correctly, and notes the accessibility constraint.
Judges when manual preservation is worth the brittleness versus redesigning (DTO/JSON/serializable hierarchy), and accounts for constructor side effects and versioning across deploys.
## Background terms **Serializable** is a marker interface (`java.io.Serializable`) that flags a class as eligible to be turned into bytes via `ObjectOutputStream` and rebuilt via `ObjectInputStream`. **A field is "inherited"** when it is declared in a superclass but accessible/visible in a subclass instance. **`writeObject`/`readObject`** are special, optional methods a serializable class may declare to take manual control of how it is written and read. ## Why parent state is lost When serialization writes an object, it climbs the inheritance chain and serializes the declared fields of **each class that itself implements Serializable**. If the superclass does NOT implement Serializable, its fields are **skipped** — they never enter the byte stream. On deserialization, Java must still construct a whole object. For the non-serializable superclass portion, since there is no data in the stream, Java calls the superclass's **no-argument constructor** and runs the normal constructor chain for that non-serializable part. The serializable subclass's fields are then populated directly from the stream (no subclass constructor runs). Result: any field that lived in the non-serializable parent is reinitialized by the parent's no-arg constructor to its default/constructed value — **not** to the value the object had at serialization time. From the outside it looks like the inherited state "vanished." ``` class Account { // NOT Serializable protected String currency; // inherited field public Account() { this.currency = "USD"; } // no-arg ctor sets default } class SavingsAccount extends Account implements Serializable { private double balance; } ``` Serialize a `SavingsAccount` whose `currency` was changed to `"EUR"`; after deserialization `currency` is back to `"USD"` (set by `Account()`), while `balance` survives because `SavingsAccount` is Serializable. ## How to preserve it The serializable subclass takes over the I/O for the parent's fields by implementing the two special methods: ```java class SavingsAccount extends Account implements Serializable { private double balance; private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { out.defaultWriteObject(); // writes SavingsAccount's own fields (balance) out.writeObject(currency); // manually write the inherited parent field } private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException { in.defaultReadObject(); // restores balance; Account() already ran this.currency = (String) in.readObject(); // overwrite the default with saved value } } ``` Key points: - `defaultWriteObject()`/`defaultReadObject()` handle the subclass's own serializable fields; you add manual writes/reads only for the parent fields you want to keep. - This is only possible if the subclass **can access** the parent fields — they must be `protected`, package-private (same package), or reachable through `public`/`protected` getters and setters. If they are `private` with no accessor, the subclass cannot read or restore them, and this technique fails. - The parent's no-arg constructor still runs first; your `readObject` then overwrites the defaults with the saved values. ## Design caveats - This pattern is **fragile**: it manually duplicates knowledge of the parent's fields; adding a field to the parent silently breaks the round-trip until you update `writeObject`/`readObject`. - Constructor side effects in the parent still fire on deserialization. - Often the better answer is to avoid relying on Java serialization for objects with non-serializable ancestors: convert to a serializable DTO, use JSON, or make the whole hierarchy serializable if you own it. ## Summary Inherited non-serializable-parent state is lost because it is never written and the parent is rebuilt via its no-arg constructor. You can preserve it by manually writing/reading those fields in the subclass's `writeObject`/`readObject` — but only when the fields are accessible, and at the cost of a brittle, hand-maintained coupling.
- What are the exact signatures of writeObject and readObject, and what access modifier should they have?private void writeObject(ObjectOutputStream out) throws IOException and private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException. They are declared private; the serialization framework invokes them reflectively.
- If the parent's fields are private with no getters/setters, can you still preserve them in the subclass?Not through ordinary subclass code, since you cannot read or assign them. You would need reflection (brittle, can hit access checks), or you should redesign - e.g. make the parent serializable or use a DTO.
saying these in an interview costs you the question
- Assuming inherited fields are saved automatically
- Believing you can restore private parent fields without accessors via writeObject
- Forgetting to call defaultReadObject/defaultWriteObject for the subclass's own fields
- Thinking marking only the subclass Serializable will carry the parent's runtime values