Why does an Externalizable class require a public no-arg constructor, and what happens at deserialization?
answer
- Two steps: no-arg ctor, then readExternal
- Constructor must be public and no-arg
- Missing it -> InvalidClassException
- Serializable bypasses constructors; Externalizable doesn't
- Read fields in the same order they were written
basics
~10 sWhen 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 sExternalizable 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
Knows Externalizable needs a public no-arg constructor and that omitting it breaks deserialization.
Explains the two-step create-then-populate process and that the constructor must be public and parameterless; can name InvalidClassException.
Contrasts the constructor-using path with Serializable's constructor-bypassing allocation and the implications for constructor side effects and final fields.
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.