ZetCode

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.

Person.java
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.

Main.java
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.

Product.java
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.

Department.java
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 + "}";
    }
}
Employee.java
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.

Vector3D.java
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.

SecureData.java
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

My name is Jan Bodnar, and I am a dedicated programmer with many years of experience in the field. I began writing programming articles in 2007 and have since authored over 1,400 articles and eight e-books. With more than eight years of teaching experience, I am committed to sharing my knowledge and helping others master programming concepts.

List all Java tutorials.