skip to content

Inheritance & Serialization

A Serializable class can extend a non-serializable one, but the parent needs an accessible no-arg constructor that runs during deserialization, and its fields are not restored. That silent loss of superclass state is the trap interviewers look for.

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

questions

5

If a Serializable class extends a non-serializable superclass, what extra requirement must the superclass meet, and why?

level: middleimportance: must knowfreq 55%

answer

  1. Parent not Serializable -> needs accessible no-arg constructor
  2. Parent's no-arg constructor RUNS during deserialization
  3. Parent fields are NOT in the stream -> not restored
  4. Missing/private no-arg ctor -> InvalidClassException at read
  5. Opposite of serializable parent (whose ctor is skipped)

basics

~20 s

The non-serializable parent must have an accessible no-argument constructor. During deserialization Java does not read the parent's fields from the stream; instead it calls that no-arg constructor to set up the parent part of the object.

solid answer

~40 s

A class can be Serializable even if its parent is not. But deserialization treats the two parts differently. The serializable subclass's fields are written to and read back from the stream. The non-serializable superclass's state is NOT in the stream at all. So to rebuild the parent portion, Java invokes the superclass's no-argument (no-arg) constructor during deserialization. Therefore that no-arg constructor must exist and be accessible to the subclass (public, protected, or package-private in the same package). If it is missing, or only private/parameterized constructors exist, deserialization throws InvalidClassException (often wrapping NotSerializableException semantics). Note the constructor actually runs, unlike for serializable superclasses whose constructors are skipped, which is a common surprise.

go deeper

for a junior

Knows a class can be Serializable without its parent being Serializable, and that the parent then needs a no-argument constructor.

for a middle

Explains that the parent's fields aren't in the stream and the parent's no-arg constructor is invoked during deserialization to rebuild that part; names InvalidClassException for a missing/inaccessible constructor.

for a senior

Contrasts serializable vs non-serializable superclass constructor behavior, reasons about lost parent state and constructor side effects, and knows how to preserve parent fields via custom writeObject/readObject.

for a principal

Weighs serialization-with-inheritance against design risks (constructor side effects firing on deserialize, security of running ctors, fragile cross-version compatibility) and steers teams toward safer alternatives (DTOs, JSON, explicit factories).

## Setup: what serialization is **Serialization** is converting a live Java object (its fields, recursively) into a byte stream so it can be saved to disk or sent over a network. **Deserialization** is the reverse: reconstructing the object from those bytes. In core Java this is done with `ObjectOutputStream.writeObject(obj)` and `ObjectInputStream.readObject()`. A class opts in by implementing the **marker interface** `java.io.Serializable` (an interface with no methods; it just flags the class as serializable). ## The scenario Consider a serializable class whose **superclass** (parent class it `extends`) is NOT serializable: ``` class Animal { // NOT Serializable String species; } class Dog extends Animal implements Serializable { // IS Serializable String name; } ``` This is **allowed** and works — but with an important asymmetry. ## What gets written vs. what gets restored When you serialize a `Dog`, the serialization machinery walks up the class hierarchy and serializes the fields of every class **in the chain that is itself Serializable**. Here only `Dog` is Serializable, so only `Dog`'s field `name` is written. The fields declared in `Animal` (e.g. `species`) are **not** written to the stream — the non-serializable superclass's state is simply not captured. On **deserialization** Java must build a complete object including the `Animal` part. Since the `Animal` fields are not in the stream, Java cannot read them back. Instead, the deserialization process **calls the no-argument constructor of the first non-serializable superclass** (here `Animal()`), and lets the normal constructor chain run for the non-serializable part. That constructor reinitializes the parent's fields to whatever defaults/values the no-arg constructor sets (so `species` will be whatever `Animal()` assigns, NOT the value the object had before serialization). The serializable subclass's fields (`name`) are then filled in from the stream — **without** running `Dog`'s constructor. ## The requirement, precisely Because Java must call that no-arg constructor, the non-serializable superclass **must have an accessible no-arg (no-argument) constructor**. "Accessible" means visible to the subclass: `public`, `protected`, or package-private when in the same package. If the superclass: - declares **no** constructors at all → the compiler supplies an implicit public no-arg constructor → fine. - declares **only** parameterized constructors → there is no no-arg constructor → **fails**. - has a no-arg constructor but it is **private** → not accessible → **fails**. When the requirement is violated, deserialization throws `java.io.InvalidClassException` complaining that the class has no valid (accessible no-arg) constructor. (Serialization, i.e. writing, generally still succeeds; the failure surfaces at read time.) ## The contrast that trips people up For a **Serializable** superclass, its constructor is **NOT** called on deserialization — the object is allocated and its fields are populated directly from the stream, bypassing constructors entirely. For a **non-serializable** superclass it is the opposite: its no-arg constructor **IS** called. So the rule of thumb is: *constructors run for the non-serializable part of the hierarchy and are skipped for the serializable part.* ## Practical consequences 1. Any state held in the non-serializable parent is **lost** across a serialize/deserialize round-trip unless you explicitly preserve it (e.g. by giving the subclass `writeObject`/`readObject` methods that read and re-set the parent's fields). 2. Side effects in the parent's no-arg constructor (logging, ID generation, registering with a registry) **will execute** during deserialization — sometimes unexpectedly. 3. If you control the parent, the cheapest fix to a deserialization failure is to add an accessible no-arg constructor.

  • What exception do you get, and at write or read time, if the non-serializable superclass lacks an accessible no-arg constructor?
    You typically get a java.io.InvalidClassException at deserialization (read) time, stating the class has no valid (accessible no-arg) constructor. Writing usually succeeds.
  • After a round-trip, what value does an inherited (parent-declared) field hold?
    Whatever the parent's no-arg constructor assigns it (or the type default if unassigned) - NOT the value the live object had before serialization, because that state was never written to the stream.

saying these in an interview costs you the question

  • Claiming the superclass must also implement Serializable - it need not
  • Saying the parent's fields are serialized and restored - they are not
  • Saying NO constructors run during deserialization - the non-serializable parent's no-arg ctor does run
  • Thinking a private or parameterized-only constructor is enough

context

open as a page

What changes if you make the previously non-serializable superclass implement Serializable too?

level: juniorimportance: should knowfreq 30%

basics

~20 s

Once the parent also implements Serializable, its fields get written to and restored from the stream like the child's. Java no longer needs to call the parent's no-arg constructor, so that constructor is no longer required and the parent's constructors are skipped during deserialization.

open as a page

During deserialization, whose constructors run and whose are skipped in a hierarchy that mixes serializable and non-serializable classes?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Constructors of serializable classes in the hierarchy are skipped; their fields are filled straight from the stream. Constructors of the non-serializable part are run, starting with the no-arg constructor of the first non-serializable superclass.

open as a page

Why is the state of an inherited (non-serializable parent) field lost after a serialize/deserialize round-trip, and how can you preserve it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

The 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.

open as a page

When designing a serializable type whose ancestors are not all serializable, what risks should you weigh and what alternatives exist?

level: principalimportance: nice to knowfreq 18%

basics

~20 s

Risks include silently lost parent state, surprising constructor side effects firing on deserialization, skipped invariant checks (a security hole), and fragile cross-version compatibility. Safer alternatives are converting to a plain data-transfer object, using JSON or another explicit format, or making the whole hierarchy serializable if you own it.

open as a page