ZetCode

Java Externalizable Interface

Last modified: April 26, 2025

The Externalizable interface in Java provides complete control over object serialization and deserialization. Unlike Serializable, it requires explicit implementation of serialization logic.

Externalizable is useful for optimizing serialization, handling complex objects, or ensuring compatibility across systems. It is ideal for applications needing fine-tuned persistence or network transfer.

Externalizable Interface Overview

The Externalizable interface, part of the java.io package, extends Serializable. It mandates implementing writeExternal and readExternal methods for custom serialization.

Classes using Externalizable must provide a no-arg constructor, as deserialization creates an instance before calling readExternal. This offers greater flexibility than Serializable.

Basic Externalizable Implementation

This example demonstrates a basic implementation of Externalizable to serialize and deserialize a simple object, saving it to a file.

BasicExternalizable.java
package com.zetcode;

import java.io.*;

public class BasicExternalizable {

    static class Product implements Externalizable {
        private String name;
        private double price;

        public Product() {} // Required for Externalizable

        public Product(String name, double price) {
            this.name = name;
            this.price = price;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeUTF(name);
            out.writeDouble(price);
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException {
            this.name = in.readUTF();
            this.price = in.readDouble();
        }

        @Override
        public String toString() {
            return "Product{name='" + name + "', price=" + price + "}";
        }
    }

    public static void main(String[] args) {
        // Serialize
        Product product = new Product("Laptop", 999.99);
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("product.ser"))) {
            oos.writeObject(product);
            System.out.println("Serialized: " + product);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Deserialize
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("product.ser"))) {
            Product deserialized = (Product) ois.readObject();
            System.out.println("Deserialized: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

The Product class implements Externalizable, defining custom serialization logic. The no-arg constructor is essential for deserialization.

This example shows how Externalizable provides precise control over which fields are serialized, ensuring efficient object persistence.

Serializing Complex Objects

This example illustrates serializing a complex object with references to other objects using Externalizable, managing the entire object graph.

ComplexExternalizable.java
package com.zetcode;

import java.io.*;

public class ComplexExternalizable {

    static class Department implements Externalizable {
        private String name;

        public Department() {}

        public Department(String name) {
            this.name = name;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeUTF(name);
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException {
            this.name = in.readUTF();
        }

        @Override
        public String toString() {
            return "Department{name='" + name + "'}";
        }
    }

    static class Employee implements Externalizable {
        private String name;
        private Department department;

        public Employee() {}

        public Employee(String name, Department department) {
            this.name = name;
            this.department = department;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeUTF(name);
            out.writeObject(department);
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            this.name = in.readUTF();
            this.department = (Department) in.readObject();
        }

        @Override
        public String toString() {
            return "Employee{name='" + name + "', department=" + department + "}";
        }
    }

    public static void main(String[] args) {
        // Serialize
        Department dept = new Department("Engineering");
        Employee emp = new Employee("Alice", dept);
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("employee.ser"))) {
            oos.writeObject(emp);
            System.out.println("Serialized: " + emp);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Deserialize
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("employee.ser"))) {
            Employee deserialized = (Employee) ois.readObject();
            System.out.println("Deserialized: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

The Employee class references a Department object, both implementing Externalizable. Custom logic handles the object graph serialization.

This demonstrates how Externalizable manages complex relationships, allowing precise control over serialization of referenced objects.

Optimizing Serialization Size

This example shows how to optimize serialization size using Externalizable by selectively serializing fields or compressing data.

OptimizedExternalizable.java
package com.zetcode;

import java.io.*;

public class OptimizedExternalizable {

    static class Book implements Externalizable {
        private String title;
        private String author;
        private transient String description; // Not serialized

        public Book() {}

        public Book(String title, String author, String description) {
            this.title = title;
            this.author = author;
            this.description = description;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeUTF(title);
            out.writeUTF(author);
            // Skip description to reduce size
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException {
            this.title = in.readUTF();
            this.author = in.readUTF();
            this.description = "N/A"; // Default value
        }

        @Override
        public String toString() {
            return "Book{title='" + title + "', author='" + author + "', description='" + description + "'}";
        }
    }

    public static void main(String[] args) {
        // Serialize
        Book book = new Book("Java Guide", "John Doe", "Comprehensive Java tutorial");
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("book.ser"))) {
            oos.writeObject(book);
            System.out.println("Serialized: " + book);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Deserialize
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("book.ser"))) {
            Book deserialized = (Book) ois.readObject();
            System.out.println("Deserialized: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

The Book class excludes the description field from serialization, reducing the serialized data size. A default value is set during deserialization.

This optimization is useful for applications where bandwidth or storage is limited, showcasing Externalizable's flexibility in data management.

Handling Versioning

This example demonstrates how Externalizable can handle class versioning by implementing logic to support backward compatibility.

VersionedExternalizable.java
package com.zetcode;

import java.io.*;

public class VersionedExternalizable {

    static class User implements Externalizable {
        private static final long serialVersionUID = 1L;
        private String username;
        private int version = 1; // Version control
        private String email; // Added in version 2

        public User() {}

        public User(String username, String email) {
            this.username = username;
            this.email = email;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeInt(version);
            out.writeUTF(username);
            if (version >= 2) {
                out.writeUTF(email);
            }
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException {
            this.version = in.readInt();
            this.username = in.readUTF();
            if (version >= 2) {
                this.email = in.readUTF();
            } else {
                this.email = "unknown@example.com"; // Default for older versions
            }
        }

        @Override
        public String toString() {
            return "User{username='" + username + "', email='" + email + "', version=" + version + "}";
        }
    }

    public static void main(String[] args) {
        // Serialize
        User user = new User("bob", "bob@example.com");
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("user.ser"))) {
            oos.writeObject(user);
            System.out.println("Serialized: " + user);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Deserialize
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("user.ser"))) {
            User deserialized = (User) ois.readObject();
            System.out.println("Deserialized: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

The User class uses a version field to manage compatibility. It conditionally serializes the email field based on the version.

This approach ensures backward compatibility, allowing deserialization of older object versions while supporting new fields in updated class definitions.

Externalizable with Non-Serializable Fields

This example shows how to handle non-serializable fields in an Externalizable class by manually managing their serialization.

NonSerializableFields.java
package com.zetcode;

import java.io.*;

public class NonSerializableFields {

    static class Logger {
        private String logLevel;

        public Logger(String logLevel) {
            this.logLevel = logLevel;
        }

        public String getLogLevel() {
            return logLevel;
        }
    }

    static class Application implements Externalizable {
        private String appName;
        private transient Logger logger;

        public Application() {}

        public Application(String appName, Logger logger) {
            this.appName = appName;
            this.logger = logger;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeUTF(appName);
            out.writeUTF(logger.getLogLevel());
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException {
            this.appName = in.readUTF();
            this.logger = new Logger(in.readUTF());
        }

        @Override
        public String toString() {
            return "Application{appName='" + appName + "', loggerLevel='" + logger.getLogLevel() + "'}";
        }
    }

    public static void main(String[] args) {
        // Serialize
        Logger logger = new Logger("INFO");
        Application app = new Application("MyApp", logger);
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("app.ser"))) {
            oos.writeObject(app);
            System.out.println("Serialized: " + app);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Deserialize
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("app.ser"))) {
            Application deserialized = (Application) ois.readObject();
            System.out.println("Deserialized: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

The Application class includes a non-serializable Logger object. Custom serialization logic handles its logLevel field.

This demonstrates how Externalizable can manage non-serializable fields by explicitly serializing their relevant data, ensuring proper object restoration.

Externalizable vs Serializable

This example compares Externalizable and Serializable by serializing the same object using both approaches, highlighting their differences.

ExternalizableVsSerializable.java
package com.zetcode;

import java.io.*;

public class ExternalizableVsSerializable {

    static class ItemSerializable implements Serializable {
        private String name;
        private int quantity;

        public ItemSerializable(String name, int quantity) {
            this.name = name;
            this.quantity = quantity;
        }

        @Override
        public String toString() {
            return "ItemSerializable{name='" + name + "', quantity=" + quantity + "}";
        }
    }

    static class ItemExternalizable implements Externalizable {
        private String name;
        private int quantity;

        public ItemExternalizable() {}

        public ItemExternalizable(String name, int quantity) {
            this.name = name;
            this.quantity = quantity;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeUTF(name);
            out.writeInt(quantity);
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException {
            this.name = in.readUTF();
            this.quantity = in.readInt();
        }

        @Override
        public String toString() {
            return "ItemExternalizable{name='" + name + "', quantity=" + quantity + "}";
        }
    }

    public static void main(String[] args) {
        // Serialize Serializable
        ItemSerializable itemSer = new ItemSerializable("Pen", 100);
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("itemSer.ser"))) {
            oos.writeObject(itemSer);
            System.out.println("Serialized Serializable: " + itemSer);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Serialize Externalizable
        ItemExternalizable itemExt = new ItemExternalizable("Pen", 100);
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("itemExt.ser"))) {
            oos.writeObject(itemExt);
            System.out.println("Serialized Externalizable: " + itemExt);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Deserialize Serializable
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("itemSer.ser"))) {
            ItemSerializable deserialized = (ItemSerializable) ois.readObject();
            System.out.println("Deserialized Serializable: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }

        // Deserialize Externalizable
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("itemExt.ser"))) {
            ItemExternalizable deserialized = (ItemExternalizable) ois.readObject();
            System.out.println("Deserialized Externalizable: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Two classes serialize the same data: one using Serializable and the other using Externalizable. Externalizable requires explicit serialization logic.

Externalizable offers more control and efficiency but requires manual implementation, while Serializable is simpler but less flexible.

Performance Considerations

This example compares the performance of Externalizable and Serializable, highlighting the efficiency of custom serialization.

PerformanceExternalizable.java
package com.zetcode;

import java.io.*;

public class PerformanceExternalizable {

    static class DataSerializable implements Serializable {
        private String data;
        private int value;

        public DataSerializable(String data, int value) {
            this.data = data;
            this.value = value;
        }
    }

    static class DataExternalizable implements Externalizable {
        private String data;
        private int value;

        public DataExternalizable() {}

        public DataExternalizable(String data, int value) {
            this.data = data;
            this.value = value;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeUTF(data);
            out.writeInt(value);
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException {
            this.data = in.readUTF();
            this.value = in.readInt();
        }
    }

    public static void main(String[] args) {
        final int COUNT = 10000;

        // Test Serializable
        long start = System.currentTimeMillis();
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("dataSer.ser"))) {
            for (int i = 0; i < COUNT; i++) {
                oos.writeObject(new DataSerializable("Test", i));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long duration = System.currentTimeMillis() - start;
        System.out.println("Serializable time: " + duration + "ms");

        // Test Externalizable
        start = System.currentTimeMillis();
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("dataExt.ser"))) {
            for (int i = 0; i < COUNT; i++) {
                oos.writeObject(new DataExternalizable("Test", i));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        duration = System.currentTimeMillis() - start;
        System.out.println("Externalizable time: " + duration + "ms");
    }
}

The program measures serialization time for Serializable and Externalizable. Externalizable is typically faster due to its minimal overhead.

Use Externalizable for performance-critical applications, but consider the added complexity of implementing custom serialization logic.

Source

Java Externalizable Documentation

This tutorial thoroughly explores the Java Externalizable interface, covering basic usage, complex objects, optimization, and versioning. It is key for custom serialization.

Author

I am Jan Bodnar, a passionate programmer with extensive experience. Since 2007, I have written over 1,400 articles and eight e-books. With over eight years of teaching, I am dedicated to sharing knowledge and helping others learn programming concepts.

List all Java tutorials.