Externalization
Externalization
Externalizable, a more manual alternative to Serializable covered in this section's previous article, hands complete control over an object's serialized form to the class itself — every field is written and read explicitly through writeExternal() and readExternal(), rather than being handled automatically through reflection.
What Is Externalizable?
Externalizable is an interface that hands a class complete manual control over its own serialized form — no reflection, no automatic field discovery, just two methods the class implements itself. Serializable writes every non-transient field automatically, with no control over the exact format or order.
1// File: BeforeExternalization.java
2import java.io.*;
3import java.nio.file.*;
4
5public class BeforeExternalization {
6
7 static class Product implements Serializable {
8 String name;
9 double price;
10 Product(String name, double price) {
11 this.name = name;
12 this.price = price;
13 }
14 }
15
16 public static void main(String[] args) throws IOException, ClassNotFoundException {
17 Product product = new Product("Keyboard", 2499.0);
18 Path tempFile = Files.createTempFile("before-ext", ".ser");
19
20 try (ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(tempFile))) {
21 out.writeObject(product);
22 }
23
24 Product restored;
25 try (ObjectInputStream in = new ObjectInputStream(Files.newInputStream(tempFile))) {
26 restored = (Product) in.readObject();
27 }
28
29 System.out.println(restored.name + ": " + restored.price);
30
31 Files.delete(tempFile);
32 }
33}Output:
Keyboard: 2499.0
Externalizable writes and reads each field explicitly, giving the class full control over exactly what gets persisted and in what order.
1// File: AfterExternalization.java
2import java.io.*;
3import java.nio.file.*;
4
5public class AfterExternalization {
6
7 static class Product implements Externalizable {
8 String name;
9 double price;
10
11 public Product() {}
12
13 Product(String name, double price) {
14 this.name = name;
15 this.price = price;
16 }
17
18 @Override
19 public void writeExternal(ObjectOutput out) throws IOException {
20 out.writeUTF(name);
21 out.writeDouble(price);
22 }
23
24 @Override
25 public void readExternal(ObjectInput in) throws IOException {
26 name = in.readUTF();
27 price = in.readDouble();
28 }
29 }
30
31 public static void main(String[] args) throws IOException, ClassNotFoundException {
32 Product product = new Product("Keyboard", 2499.0);
33 Path tempFile = Files.createTempFile("after-ext", ".ser");
34
35 try (ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(tempFile))) {
36 out.writeObject(product);
37 }
38
39 Product restored;
40 try (ObjectInputStream in = new ObjectInputStream(Files.newInputStream(tempFile))) {
41 restored = (Product) in.readObject();
42 }
43
44 System.out.println(restored.name + ": " + restored.price);
45
46 Files.delete(tempFile);
47 }
48}Output:
Keyboard: 2499.0
Both produce the same result here, but Product in the second version now requires a public no-arg constructor — Externalizable's single mandatory requirement, covered next.
How It Works Internally
Unlike Serializable, which restores an object's fields directly via reflection with no constructor involved at all, an Externalizable object is genuinely constructed first — through its public no-arg constructor — and only then does readExternal() populate it.
One sentence before the diagram: deserialization is a strict two-step handoff for Externalizable, and skipping either step is impossible — the object must exist before it can be filled in.
ObjectInputStream.readObject() for an Externalizable class:
Step 1: call the public no-arg constructor
--> a bare, empty Product object now exists
Step 2: call readExternal(objectInput) on that object
--> the class's own code fills in every field itself,
reading in whatever order writeExternal() wrote them
Without that constructor, deserialization fails immediately.
1// File: MissingNoArgConstructorMistake.java
2import java.io.*;
3import java.nio.file.*;
4
5public class MissingNoArgConstructorMistake {
6
7 static class Broken implements Externalizable {
8 String value;
9
10 Broken(String value) {
11 this.value = value;
12 }
13
14 @Override
15 public void writeExternal(ObjectOutput out) throws IOException {
16 out.writeUTF(value);
17 }
18
19 @Override
20 public void readExternal(ObjectInput in) throws IOException {
21 value = in.readUTF();
22 }
23 }
24
25 public static void main(String[] args) throws IOException, ClassNotFoundException {
26 Broken original = new Broken("data");
27 Path tempFile = Files.createTempFile("missing-ctor", ".ser");
28
29 try (ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(tempFile))) {
30 out.writeObject(original);
31 }
32
33 try (ObjectInputStream in = new ObjectInputStream(Files.newInputStream(tempFile))) {
34 in.readObject();
35 } catch (InvalidClassException e) {
36 System.out.println("Caught: " + e.getClass().getSimpleName());
37 }
38
39 Files.delete(tempFile);
40 }
41}Output:
Caught: InvalidClassException
Writing succeeds, since writeExternal() never needs to construct anything. Reading fails, because ObjectInputStream has no way to create a fresh Broken instance before readExternal() could populate it.
Externalizable skips the reflection-based field discovery Serializable relies on, which is exactly why it suits a custom binary protocol, performance-sensitive serialization, or selectively excluding fields without a transient declaration for each one.
Real-World Example
A fleet-tracking system serializes a vehicle's GPS location ping, giving explicit control over exactly which fields are written and in what order — the same construct-then-populate sequence this article's internals section walks through.
1// File: LocationPing.java
2import java.io.*;
3
4public class LocationPing implements Externalizable {
5 private String vehicleId;
6 private double latitude;
7 private double longitude;
8 private long timestamp;
9
10 public LocationPing() {}
11
12 public LocationPing(String vehicleId, double latitude, double longitude, long timestamp) {
13 this.vehicleId = vehicleId;
14 this.latitude = latitude;
15 this.longitude = longitude;
16 this.timestamp = timestamp;
17 }
18
19 @Override
20 public void writeExternal(ObjectOutput out) throws IOException {
21 out.writeUTF(vehicleId);
22 out.writeDouble(latitude);
23 out.writeDouble(longitude);
24 out.writeLong(timestamp);
25 }
26
27 @Override
28 public void readExternal(ObjectInput in) throws IOException {
29 vehicleId = in.readUTF();
30 latitude = in.readDouble();
31 longitude = in.readDouble();
32 timestamp = in.readLong();
33 }
34
35 public String getVehicleId() { return vehicleId; }
36 public double getLatitude() { return latitude; }
37 public double getLongitude() { return longitude; }
38 public long getTimestamp() { return timestamp; }
39}1// File: LocationPingDemo.java
2import java.io.*;
3import java.nio.file.*;
4
5public class LocationPingDemo {
6 public static void main(String[] args) throws IOException, ClassNotFoundException {
7 LocationPing ping = new LocationPing("TRK-4021", 12.9716, 77.5946, 1755000000L);
8
9 Path tempFile = Files.createTempFile("ping", ".ser");
10
11 try (ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(tempFile))) {
12 out.writeObject(ping);
13 }
14
15 LocationPing restored;
16 try (ObjectInputStream in = new ObjectInputStream(Files.newInputStream(tempFile))) {
17 restored = (LocationPing) in.readObject();
18 }
19
20 System.out.println(restored.getVehicleId() + " at (" + restored.getLatitude() + ", " + restored.getLongitude() + ")");
21 System.out.println("Timestamp: " + restored.getTimestamp());
22
23 Files.delete(tempFile);
24 }
25}Output:
TRK-4021 at (12.9716, 77.5946)
Timestamp: 1755000000
A mistake that appears often in fresher pull requests is assuming Externalizable's manual control over the wire format also means a class no longer needs a public no-arg constructor, since the class itself is doing the actual reading and writing. It still does — ObjectInputStream must be able to instantiate LocationPing before readExternal() has any object at all to populate.
Best Practices
Always declare a public no-arg constructor on an Externalizable class — it is not optional, and its absence fails only at deserialization time, not at compile time.
Read fields in readExternal() in exactly the same order they were written in writeExternal(), since nothing in the format itself labels which value is which.
Call super.writeExternal() and super.readExternal() explicitly when extending another Externalizable class, since — unlike Serializable — there is no automatic chaining to a superclass's serialization logic at all.
Prefer Serializable for straightforward cases, and reach for Externalizable specifically when the format itself needs to be controlled — for a simple data class with no unusual requirements, the extra code Externalizable demands rarely pays for itself.
Common Mistakes
Reading fields back in a different order than they were written does not throw — it silently assigns the wrong value to the wrong field.
1// File: FieldOrderMismatchMistake.java
2import java.io.*;
3import java.nio.file.*;
4
5public class FieldOrderMismatchMistake {
6
7 static class Coordinates implements Externalizable {
8 int x;
9 int y;
10
11 public Coordinates() {}
12
13 Coordinates(int x, int y) {
14 this.x = x;
15 this.y = y;
16 }
17
18 @Override
19 public void writeExternal(ObjectOutput out) throws IOException {
20 out.writeInt(x);
21 out.writeInt(y);
22 }
23
24 @Override
25 public void readExternal(ObjectInput in) throws IOException {
26 y = in.readInt();
27 x = in.readInt();
28 }
29 }
30
31 public static void main(String[] args) throws IOException, ClassNotFoundException {
32 Coordinates original = new Coordinates(10, 20);
33 Path tempFile = Files.createTempFile("order-mismatch", ".ser");
34
35 try (ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(tempFile))) {
36 out.writeObject(original);
37 }
38
39 Coordinates restored;
40 try (ObjectInputStream in = new ObjectInputStream(Files.newInputStream(tempFile))) {
41 restored = (Coordinates) in.readObject();
42 }
43
44 System.out.println("Original: (" + original.x + ", " + original.y + ")");
45 System.out.println("Restored: (" + restored.x + ", " + restored.y + ")");
46
47 Files.delete(tempFile);
48 }
49}Output:
Original: (10, 20)
Restored: (20, 10)
writeExternal() writes x then y, but readExternal() reads the first value into y and the second into x — the coordinates come back swapped, with no exception or warning of any kind.
Forgetting to call a superclass's writeExternal() and readExternal() explicitly loses that superclass's fields entirely, since Externalizable does not chain to a superclass automatically.
1// File: MissingSuperCallMistake.java
2import java.io.*;
3import java.nio.file.*;
4
5public class MissingSuperCallMistake {
6
7 static class Base implements Externalizable {
8 String category;
9
10 public Base() {}
11
12 Base(String category) {
13 this.category = category;
14 }
15
16 @Override
17 public void writeExternal(ObjectOutput out) throws IOException {
18 out.writeUTF(category);
19 }
20
21 @Override
22 public void readExternal(ObjectInput in) throws IOException {
23 category = in.readUTF();
24 }
25 }
26
27 static class Item extends Base {
28 String name;
29
30 public Item() {}
31
32 Item(String category, String name) {
33 super(category);
34 this.name = name;
35 }
36
37 @Override
38 public void writeExternal(ObjectOutput out) throws IOException {
39 out.writeUTF(name);
40 }
41
42 @Override
43 public void readExternal(ObjectInput in) throws IOException {
44 name = in.readUTF();
45 }
46 }
47
48 public static void main(String[] args) throws IOException, ClassNotFoundException {
49 Item original = new Item("Electronics", "Keyboard");
50 Path tempFile = Files.createTempFile("missing-super", ".ser");
51
52 try (ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(tempFile))) {
53 out.writeObject(original);
54 }
55
56 Item restored;
57 try (ObjectInputStream in = new ObjectInputStream(Files.newInputStream(tempFile))) {
58 restored = (Item) in.readObject();
59 }
60
61 System.out.println("Original category: " + original.category);
62 System.out.println("Restored category: " + restored.category);
63 System.out.println("Restored name: " + restored.name);
64
65 Files.delete(tempFile);
66 }
67}Output:
Original category: Electronics
Restored category: null
Restored name: Keyboard
Item.writeExternal() never writes category, and Item.readExternal() never reads it back — Base's own logic is simply never invoked. The restored object's category ends up null, since Base's no-arg constructor leaves it unset, with no connection at all to the "Electronics" value the original object actually held.
Interview Questions
Q1. What is the difference between Serializable and Externalizable?
Serializable is a marker interface with no methods — Java's default mechanism writes and reads every non-transient field automatically via reflection. Externalizable requires the class to implement writeExternal() and readExternal() itself, taking complete manual control over exactly what is persisted and how. The nuance interviewers listen for is whether you can explain the tradeoff, not just name both interfaces.
Q2. Why does Externalizable require a public no-arg constructor?
Because ObjectInputStream must construct a fresh instance of the class before readExternal() has any object at all to populate — unlike Serializable, which restores fields directly via reflection with no constructor call involved. This is the single most-tested fact about Externalizable, precisely because it's easy to overlook.
Q3. What happens if an Externalizable class has no public no-arg constructor?
Deserialization fails with InvalidClassException, exactly as demonstrated in this article's MissingNoArgConstructorMistake example — writing still succeeds, since writeExternal() never needs to construct anything. Interviewers listen for whether you know writing and reading fail independently here.
Q4. Does Externalizable automatically serialize a superclass's fields?
No. Unlike Serializable's automatic chaining through a class hierarchy, an Externalizable subclass must explicitly call its superclass's writeExternal() and readExternal() — omitting that call silently loses the superclass's fields entirely, as shown in this article's MissingSuperCallMistake example. This contrast with Serializable is exactly what separates a strong answer from a surface-level one.
Q5. What happens if readExternal() reads fields in a different order than writeExternal() wrote them?
Nothing throws — values are simply assigned to the wrong fields, exactly as demonstrated in this article's FieldOrderMismatchMistake example, where two coordinate values come back swapped with no warning at all. Product-company interviewers often follow up asking how you'd catch this in code review, since there's no compiler safety net.
Q6. Why might a class choose Externalizable over Serializable?
To gain full control over the exact byte format, to avoid the reflection overhead Serializable's automatic mechanism relies on, or to selectively transform or exclude fields without needing a separate transient declaration for each one.
Q7. Can Externalizable be used with a record?
Not practically. A record's canonical constructor sets every component at once, which conflicts with Externalizable's required two-step process — an empty no-arg construction followed by manual population in readExternal(). The nuance being tested is understanding why the two designs are fundamentally incompatible, not just that they don't mix.
FAQs
Does Externalizable extend Serializable?
Yes, Externalizable extends Serializable, which is why an Externalizable object can still be passed to ObjectOutputStream.writeObject() and read back with ObjectInputStream.readObject() exactly like an ordinary serializable object.
Is Externalizable faster than Serializable?
Generally yes, since it skips the reflection-based field discovery Serializable's automatic mechanism relies on — the actual performance difference depends on how much data is involved and how the class's writeExternal() and readExternal() are implemented.
Can Externalizable produce a smaller serialized format than Serializable?
Often yes, since the class controls exactly which fields are written and how, without the additional class-descriptor metadata Serializable's automatic mechanism includes for reflection purposes — though the exact size difference depends on the specific class and data involved.
Is transient relevant to an Externalizable class?
No, not in the way it is for Serializable. Since writeExternal() explicitly decides what gets written, a field is simply omitted from that method's body if it should not be persisted — the transient keyword has no effect on Externalizable's behavior.
Does Externalizable check serialVersionUID the same way Serializable does?
Yes, Externalizable classes can and generally should declare serialVersionUID too — the same version-compatibility check applies, since Externalizable still goes through ObjectOutputStream and ObjectInputStream.
Can writeExternal() throw a checked exception?
Yes, it declares throws IOException, and any IOException thrown propagates out of the enclosing writeObject() call, exactly as it would for any other stream-writing failure.
Is Externalizable commonly used in modern Java code?
Not especially — most modern serialization needs are met either by Serializable for simple cases or by an external format like JSON for anything needing cross-language compatibility or long-term stability. Externalizable remains most relevant for a custom binary protocol or a case where the exact byte format genuinely needs to be controlled.
Summary
Externalizable trades Serializable's automatic, reflection-based field handling for complete manual control — every field is written and read explicitly through writeExternal() and readExternal(), at the cost of a mandatory public no-arg constructor and the responsibility to keep read and write order in sync by hand.
The habit worth carrying forward from this article's GPS location example is treating writeExternal() and readExternal() as a matched pair that must agree on field order exactly, and remembering that neither the no-arg constructor requirement nor superclass chaining happens automatically the way they would with Serializable.
What to Read Next
See how serialization and externalization are different.