Java Externalizable Interface
Last modified: April 16, 2025
The java.io.Externalizable
interface provides custom serialization
control for Java objects. It extends Serializable
and allows
complete control over the serialization process. Classes implement two methods:
writeExternal
and readExternal
.
Externalizable
differs from standard Java serialization by giving
developers full responsibility for writing and reading object state. This enables
optimized serialization formats and versioning control. The interface is useful
for performance-critical applications.
Externalizable Interface Overview
The Externalizable
interface defines two methods that must be
implemented. These methods handle writing object state to a stream and reading
it back. The interface provides more control than default serialization.
public interface Externalizable extends Serializable { void writeExternal(ObjectOutput out) throws IOException; void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; }
The code above shows the Externalizable
interface declaration.
writeExternal
writes object data to an ObjectOutput
.
readExternal
reads data from an ObjectInput
to
reconstruct the object.
Basic Externalizable Implementation
This example demonstrates a simple class implementing Externalizable
.
The class controls exactly which fields are serialized and how. This provides
better performance than default serialization.
import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; public class Person implements Externalizable { private String name; private int age; private transient String password; // Not serialized public Person() {} // Required public no-arg constructor public Person(String name, int age, String password) { this.name = name; this.age = age; this.password = password; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeUTF(name); out.writeInt(age); // password is not written } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = in.readUTF(); age = in.readInt(); password = "default"; // Set default value } @Override public String toString() { return "Person{name='" + name + "', age=" + age + ", password='" + password + "'}"; } }
This Person
class implements Externalizable
with custom
serialization. The password
field is marked transient and excluded.
A public no-arg constructor is required for Externalizable
classes.
The toString
method helps demonstrate object state.
Serializing and Deserializing an Externalizable Object
This example shows how to serialize and deserialize an Externalizable
object. The process uses ObjectOutputStream
and
ObjectInputStream
like standard serialization.
import java.io.*; public class Main { public static void main(String[] args) { Person person = new Person("Alice", 30, "secret123"); // Serialize try (ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("person.ser"))) { oos.writeObject(person); System.out.println("Serialized: " + person); } catch (IOException e) { e.printStackTrace(); } // Deserialize try (ObjectInputStream ois = new ObjectInputStream( new FileInputStream("person.ser"))) { Person deserialized = (Person) ois.readObject(); System.out.println("Deserialized: " + deserialized); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
This code serializes a Person
object to a file and reads it back.
The output shows the object state before serialization and after deserialization.
Note the password field is reset to "default" during deserialization as it wasn't
saved. The try-with-resources ensures proper stream handling.
Version Control with Externalizable
Externalizable
provides better version control than standard
serialization. You can manually handle different versions of your class format.
This example demonstrates version-aware serialization.
import java.io.*; public class Product implements Externalizable { private static final long serialVersionUID = 1L; private static final int CURRENT_VERSION = 2; private String name; private double price; private int quantity; // Added in version 2 public Product() {} public Product(String name, double price, int quantity) { this.name = name; this.price = price; this.quantity = quantity; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(CURRENT_VERSION); out.writeUTF(name); out.writeDouble(price); out.writeInt(quantity); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { int version = in.readInt(); name = in.readUTF(); price = in.readDouble(); if (version >= 2) { quantity = in.readInt(); } else { quantity = 1; // Default for old versions } } @Override public String toString() { return "Product{name='" + name + "', price=" + price + ", quantity=" + quantity + "}"; } }
This Product
class demonstrates version control. The
CURRENT_VERSION
constant tracks the serialization format version.
When reading, older versions are handled by providing default values for new
fields. This approach maintains backward compatibility with older serialized
data.
Complex Object Graph with Externalizable
Externalizable
can handle complex object graphs with references to
other objects. Each referenced object must also implement Serializable
or Externalizable
. This example shows a Department with Employees.
import java.io.*; import java.util.ArrayList; import java.util.List; public class Department implements Externalizable { private String name; private List<Employee> employees; public Department() { employees = new ArrayList<>(); } public Department(String name) { this(); this.name = name; } public void addEmployee(Employee emp) { employees.add(emp); } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeUTF(name); out.writeInt(employees.size()); for (Employee emp : employees) { out.writeObject(emp); } } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = in.readUTF(); int size = in.readInt(); employees = new ArrayList<>(size); for (int i = 0; i < size; i++) { employees.add((Employee) in.readObject()); } } @Override public String toString() { return "Department{name='" + name + "', employees=" + employees + "}"; } }
import java.io.*; public class Employee implements Externalizable { private String name; private int id; public Employee() {} public Employee(String name, int id) { this.name = name; this.id = id; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeUTF(name); out.writeInt(id); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = in.readUTF(); id = in.readInt(); } @Override public String toString() { return "Employee{name='" + name + "', id=" + id + "}"; } }
This example shows a Department
containing multiple
Employee
objects. Both classes implement Externalizable
.
The Department
writes its name and then each employee. When reading,
it first reads the employee count before reconstructing the list. This maintains
the object graph structure.
Performance Optimization with Externalizable
Externalizable
can significantly improve serialization performance
by optimizing the data format. This example shows a high-performance
implementation for a 3D vector class.
import java.io.*; public class Vector3D implements Externalizable { private float x, y, z; public Vector3D() {} public Vector3D(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } @Override public void writeExternal(ObjectOutput out) throws IOException { // Write as single byte array for compactness byte[] data = new byte[12]; intToBytes(Float.floatToIntBits(x), data, 0); intToBytes(Float.floatToIntBits(y), data, 4); intToBytes(Float.floatToIntBits(z), data, 8); out.write(data); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { byte[] data = new byte[12]; in.readFully(data); x = Float.intBitsToFloat(bytesToInt(data, 0)); y = Float.intBitsToFloat(bytesToInt(data, 4)); z = Float.intBitsToFloat(bytesToInt(data, 8)); } private void intToBytes(int value, byte[] dest, int offset) { dest[offset] = (byte)(value >> 24); dest[offset+1] = (byte)(value >> 16); dest[offset+2] = (byte)(value >> 8); dest[offset+3] = (byte)value; } private int bytesToInt(byte[] src, int offset) { return ((src[offset] & 0xFF) << 24) | ((src[offset+1] & 0xFF) << 16) | ((src[offset+2] & 0xFF) << 8) | (src[offset+3] & 0xFF); } @Override public String toString() { return String.format("Vector3D(%.2f, %.2f, %.2f)", x, y, z); } }
This Vector3D
class demonstrates performance optimization. Instead
of writing three separate float values, it packs them into a compact byte array.
This reduces serialization overhead and storage space. The helper methods handle
conversion between float/int and byte representations.
Security Considerations with Externalizable
Externalizable
requires careful security implementation since you
control the serialization process. This example shows secure handling of
sensitive data with encryption.
import java.io.*; import javax.crypto.*; import javax.crypto.spec.SecretKeySpec; import java.security.*; import java.util.Base64; public class SecureData implements Externalizable { private static final String ALGORITHM = "AES"; private static final byte[] KEY = "MySuperSecretKey".getBytes(); private String sensitiveData; public SecureData() {} public SecureData(String sensitiveData) { this.sensitiveData = sensitiveData; } @Override public void writeExternal(ObjectOutput out) throws IOException { try { Cipher cipher = Cipher.getInstance(ALGORITHM); SecretKeySpec keySpec = new SecretKeySpec(KEY, ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] encrypted = cipher.doFinal(sensitiveData.getBytes()); out.writeUTF(Base64.getEncoder().encodeToString(encrypted)); } catch (Exception e) { throw new IOException("Encryption failed", e); } } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { try { String encryptedStr = in.readUTF(); byte[] encrypted = Base64.getDecoder().decode(encryptedStr); Cipher cipher = Cipher.getInstance(ALGORITHM); SecretKeySpec keySpec = new SecretKeySpec(KEY, ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, keySpec); byte[] decrypted = cipher.doFinal(encrypted); sensitiveData = new String(decrypted); } catch (Exception e) { throw new IOException("Decryption failed", e); } } public String getSensitiveData() { return sensitiveData; } }
This SecureData
class demonstrates secure serialization. Sensitive
data is encrypted before writing and decrypted after reading. Note that in a real
application, the encryption key should be properly secured. The example uses
Base64 encoding for safe transport of binary data.
Source
Java Externalizable Interface Documentation
In this article, we've covered the essential methods and features of the Java Externalizable interface. Understanding these concepts is crucial for working with custom serialization in Java applications.
Author
List all Java tutorials.