skip to content

Serialization

Java's native serialization mechanism and its surrounding rules: version identity, transient fields, the customization hooks, Externalizable, inheritance behavior, security and alternatives. Interviewers treat it as a topic you should know well enough to argue against using.

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

questions

10

Why does an Externalizable class require a public no-arg constructor, and what happens at deserialization?

level: middleimportance: must knowfreq 50%

answer

  1. Two steps: no-arg ctor, then readExternal
  2. Constructor must be public and no-arg
  3. Missing it -> InvalidClassException
  4. Serializable bypasses constructors; Externalizable doesn't
  5. Read fields in the same order they were written

basics

~10 s

When restoring an Externalizable object, Java first creates an empty instance by calling its public no-arg constructor, then calls readExternal to fill in the data. Without that constructor, deserialization fails with InvalidClassException.

solid answer

~40 s

Externalizable deserialization happens in two steps. First, ObjectInputStream creates a fresh, empty instance by invoking the class's public no-arg constructor — the ordinary object-creation path. Second, it calls your readExternal(ObjectInput), where you read the values back in the same order they were written and assign them to the new object's fields. Because step one uses a real constructor, that constructor must exist and be public; if it's missing or not accessible, you get an InvalidClassException at read time. This contrasts sharply with Serializable, which uses a special JVM allocation path that bypasses constructors entirely and restores fields directly. So with Externalizable any logic in your no-arg constructor will run, whereas with Serializable no constructor of the serializable class runs at all. This is a common interview gotcha.

go deeper

for a junior

Knows Externalizable needs a public no-arg constructor and that omitting it breaks deserialization.

for a middle

Explains the two-step create-then-populate process and that the constructor must be public and parameterless; can name InvalidClassException.

for a senior

Contrasts the constructor-using path with Serializable's constructor-bypassing allocation and the implications for constructor side effects and final fields.

for a principal

Reasons about reconstruction invariants across mechanisms — e.g. validation/initialization that must not be silently skipped — and how this informs choosing or wrapping serialization strategies.

## Setup: what deserialization means **Deserialization** is reconstructing a live object from the byte stream produced earlier. Java does this through `ObjectInputStream`. *How* the object is reconstructed differs between the two opt-in mechanisms. ## Externalizable: construct, then populate When `ObjectInputStream` reads bytes for a class that implements `java.io.Externalizable`, it does **two distinct things**: 1. **Instantiate via the public no-arg constructor.** The runtime calls `new YourClass()` through the normal reflective constructor path. This produces a blank object whose fields hold their default/initializer values, and **any code in that constructor actually runs**. 2. **Populate via `readExternal`.** It then calls your `readExternal(ObjectInput in)`, where you pull values out of the stream — `in.readInt()`, `in.readObject()`, etc. — **in the exact order they were written** by `writeExternal`, and assign them to fields. Because step 1 is a genuine constructor call, the class **must declare a `public` no-arg constructor**. Requirements: - It must take **no arguments**. - It must be **`public`** (not package-private/protected/private) — the deserializing code is in `java.io`, a different package, and needs access. - If you write *any* other constructor, the compiler will not auto-generate the default one, so you must add the no-arg constructor explicitly. If the constructor is missing or not public, deserialization fails with: ``` java.io.InvalidClassException: ...; no valid constructor ``` ## Serializable: no constructor at all For a plain `Serializable` class, the JVM uses a **special allocation mechanism** (historically backed by `sun.reflect.ReflectionFactory` / `Unsafe`) that creates the instance **without invoking any constructor of the serializable class**. It then sets fields directly from the stream. Consequences: - Constructor side effects (validation, ID generation, logging) **do not run** on deserialization. - Final fields can still be set by the runtime. - (Edge detail: the no-arg constructor of the **first non-serializable superclass** *is* called, but no constructor of the serializable class itself.) ## Side-by-side | | `Externalizable` | `Serializable` | |---|---|---| | Object creation | public no-arg constructor (**runs**) | special allocation (**no ctor of the class**) | | Field population | your `readExternal` | runtime reflection / `readObject` | | Missing no-arg ctor | `InvalidClassException` | irrelevant | ## Worked example ```java class Session implements Externalizable { private String user; private long ts; public Session() { // REQUIRED, public, no-arg System.out.println("ctor runs on deserialize"); } Session(String user) { // app constructor this.user = user; this.ts = System.currentTimeMillis(); } public void writeExternal(ObjectOutput out) throws IOException { out.writeUTF(user); out.writeLong(ts); } public void readExternal(ObjectInput in) throws IOException { user = in.readUTF(); // SAME order as written ts = in.readLong(); } } ``` On read: "ctor runs on deserialize" prints (proving the constructor fires), then `readExternal` restores the two fields. ## Why it matters This difference is a classic interview trap and a real correctness concern: code that relies on constructor side effects behaves differently under the two mechanisms, and forgetting the public no-arg constructor is the most common Externalizable bug.

  • Does the no-arg constructor's body actually execute during deserialization?
    Yes — Externalizable uses the real constructor path, so any code in the public no-arg constructor runs before readExternal populates the object.
  • Does Serializable call the serializable class's constructor on read?
    No. It uses a special allocation path that bypasses the class's constructors; only the first non-serializable superclass's no-arg constructor is invoked.

saying these in an interview costs you the question

  • Saying any constructor works — it must be the public no-arg one specifically.
  • Claiming the constructor body is skipped for Externalizable (it runs).
  • Believing Serializable also calls the class's no-arg constructor.
  • Reading fields in a different order than they were written in readExternal.

context

open as a page

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

level: middleimportance: must knowfreq 55%

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.

open as a page

What is Externalizable in Java, and how does it differ from Serializable?

level: juniorimportance: should knowfreq 45%

basics

~10 s

Both let an object be saved as bytes and restored. Serializable saves fields automatically; Externalizable makes you write the saving and loading code yourself in writeExternal and readExternal, giving full control of the format.

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

How do you correctly implement writeExternal and readExternal, and what are the common pitfalls?

level: middleimportance: should knowfreq 40%

basics

~20 s

In writeExternal you write each value you want to keep; in readExternal you read them back in exactly the same order and assign them to fields. The biggest pitfall is reading in a different order or count than you wrote.

open as a page

What are the performance and maintainability trade-offs of Externalizable versus Serializable, and when would you choose each?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Externalizable can produce smaller, faster output because you control the exact format and skip reflection and metadata, but it's more code and easier to break. Serializable is simpler and safer to maintain but slower and larger. Use Serializable unless you've measured a real need.

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

How does Externalizable interact with serialization edge cases like readResolve, singletons, and final fields?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Externalizable still supports readResolve to swap the deserialized object for another instance, which matters for singletons. But because it constructs via a public no-arg constructor and you assign fields yourself, truly final fields can't be set in readExternal, so final fields don't work the same as with Serializable.

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