1. Introduction
In Java, the interface Serializable is most often used for object serialization. It is simple: just implement the interface and the object can be written/read using ObjectOutputStream/ObjectInputStream. But sometimes this is not enough:
- You need full control over which fields are serialized and how.
- You must ensure compatibility across different versions of a class.
- It is important to reduce the size of the serialized file or speed up the process.
For such cases, Java has the Externalizable interface — a more “manual” and flexible way of serialization.
In short:
- Serializable — automatic serialization: Java itself decides what and how to write.
- Externalizable — manual serialization: you specify what and how to save/restore.
2. The Externalizable contract: implement writeExternal and readExternal
To use Externalizable, you need to:
- Implement the java.io.Externalizable interface.
- Implement two methods:
- void writeExternal(ObjectOutput out) throws IOException
- void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
Example:
import java.io.*;
public class User implements Externalizable {
private String name;
private int age;
// Mandatory public no-arg constructor!
public User() {}
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = in.readUTF();
age = in.readInt();
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
Important: the developer decides which fields will be serialized and in what order. However, there is one mandatory requirement: the class must have a public no-argument constructor. If it is missing, deserialization will throw InvalidClassException.
3. When should you use Externalizable?
Use Externalizable if:
- You need full control over the data format. For example, you want to serialize only some fields or serialize them in a special order/format.
- You want to optimize performance and file size. Standard serialization adds overhead (metadata, class names, types, etc.). With Externalizable you write only the data you need.
- You need backward compatibility. If the class structure changes, you can implement logic to read both old and new versions of the data manually.
- You need to serialize nonstandard objects. For example, if you have fields that cannot be serialized in the standard way (e.g., transient, volatile, or complex structures).
When should you NOT use it?
- If you do not need full control — use Serializable; it is simpler and safer.
- If you are not sure you can maintain data format compatibility when the class changes.
4. Pros and cons of Externalizable compared to Serializable
Pros:
- Full control over serialization. You decide what and how to write/read.
- Compactness. No extra metadata — only your data.
- Speed. Less data — faster write/read.
- Flexibility. You can implement support for different format versions, add compression, encryption, etc.
Cons:
- Manual implementation — it is easy to make a mistake. If you mix up the write/read order, serialization will “break” (you will get an error or incorrect data).
- No automatic support for transient, serialVersionUID. Everything must be designed and implemented manually.
- Harder to maintain. When the class structure changes, you must remember to update the serialization methods.
- A public no-argument constructor is required.
- Less “magic” — more responsibility.
5. Examples: serialization and deserialization of a simple object
Serializing an object
User user = new User("Alice", 30);
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.bin"))) {
out.writeObject(user);
}
Deserializing an object
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.bin"))) {
User loaded = (User) in.readObject();
System.out.println(loaded); // Alice (30)
}
Warning: if you change the order of writing/reading fields or forget to serialize a field, the data will be incorrect! The writeExternal and readExternal methods must be strictly aligned in the sequence of operations.
Example: serialize only a subset of fields
public class SecretUser implements Externalizable {
private String login;
private transient String password; // transient has no effect with Externalizable
public SecretUser() {}
public SecretUser(String login, String password) {
this.login = login;
this.password = password;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(login);
// Do not serialize the password!
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
login = in.readUTF();
password = null; // do not restore the password
}
}
6. Practice: comparing the size of the serialized file
Let’s compare how much the files “weigh” when serialized via Serializable versus via Externalizable.
Class with Serializable
public class UserSerializable implements Serializable {
private String name;
private int age;
public UserSerializable(String name, int age) {
this.name = name;
this.age = age;
}
}
Class with Externalizable
public class UserExternalizable implements Externalizable {
private String name;
private int age;
public UserExternalizable() {}
public UserExternalizable(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = in.readUTF();
age = in.readInt();
}
}
Code to compare
import java.io.*;
public class CompareSerialization {
public static void main(String[] args) throws Exception {
UserSerializable s = new UserSerializable("Bob", 25);
UserExternalizable e = new UserExternalizable("Bob", 25);
// Serializable
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"))) {
out.writeObject(s);
}
// Externalizable
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ext.bin"))) {
out.writeObject(e);
}
System.out.println("Serializable file size: " + new File("ser.bin").length());
System.out.println("Externalizable file size: " + new File("ext.bin").length());
}
}
Result:
The ser.bin file (Serializable) is usually larger — it contains Java metadata/overhead. The ext.bin file (Externalizable) contains only your data and is usually smaller.
7. Common mistakes when working with Externalizable
Error #1: Missing public no-argument constructor.
A class that implements Externalizable must have a public constructor without arguments. Without it, deserialization will throw InvalidClassException.
Error #2: Violating the order of field writes and reads.
The writeExternal and readExternal methods must operate in the same order. If you write the name field first but try to read age first, the data will be corrupted.
Error #3: Fields omitted during serialization.
If you forget to write a field in writeExternal, upon deserialization it will have the value null (for reference types) or 0 (for numeric types).
Error #4: Incorrect use of transient or serialVersionUID.
Unlike Serializable, with Externalizable these mechanisms do not work automatically — you must explicitly control which fields to save and which to skip.
Error #5: Changing the class structure without updating the methods.
If you add or remove fields and do not make the corresponding changes to writeExternal and readExternal, old saved data may stop loading correctly.
GO TO FULL VERSION