Java > Java Input/Output (I/O) > Serialization and Deserialization > Externalizable Interface

Custom Serialization with Externalizable Interface

This example demonstrates how to use the Externalizable interface in Java to customize the serialization and deserialization process. Unlike Serializable, Externalizable gives you complete control over what fields are written and read during serialization, allowing you to optimize the process and handle versioning effectively. This is crucial when dealing with sensitive data or when needing fine-grained control over object persistence.

Defining the Externalizable Class

This code defines a class ExternalizableExample that implements the Externalizable interface. It contains a name, age, and a secret field. The writeExternal method explicitly writes the name and age, but intentionally excludes the secret, demonstrating how you can control what is serialized. The readExternal method reads the name and age, and then sets secret to an empty string to avoid a null value.

import java.io.*;

public class ExternalizableExample implements Externalizable {

    private String name;
    private int age;
    private String secret;

    // Required: Public no-arg constructor
    public ExternalizableExample() {
    }

    public ExternalizableExample(String name, int age, String secret) {
        this.name = name;
        this.age = age;
        this.secret = secret;  // Simulate a sensitive field
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    // We intentionally don't provide a getter for 'secret' to demonstrate hiding during serialization.

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeInt(age);
        // 'secret' is deliberately NOT written.  Consider encryption here for real secrets.
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        age = in.readInt();
        // 'secret' is deliberately NOT read.  It will remain null or its default value.
        // We may need to initialize it here based on name/age or some other logic.
        secret = ""; //default value instead of null
    }

    @Override
    public String toString() {
        return "ExternalizableExample{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", secret='" + secret + '\'' +
                '}';
    }

    public static void main(String[] args) {
        ExternalizableExample original = new ExternalizableExample("Alice", 30, "TopSecret");

        // Serialization
        try (FileOutputStream fileOut = new FileOutputStream("externalizable.ser");
             ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
            out.writeObject(original);
            System.out.println("Serialized data is saved in externalizable.ser");
        } catch (IOException i) {
            i.printStackTrace();
        }

        // Deserialization
        ExternalizableExample deserialized = null;
        try (FileInputStream fileIn = new FileInputStream("externalizable.ser");
             ObjectInputStream in = new ObjectInputStream(fileIn)) {
            deserialized = (ExternalizableExample) in.readObject();
        } catch (IOException i) {
            i.printStackTrace();
            return;
        } catch (ClassNotFoundException c) {
            System.out.println("ExternalizableExample class not found");
            c.printStackTrace();
            return;
        }

        System.out.println("Deserialized ExternalizableExample: " + deserialized);
    }
}

Concepts Behind Externalizable

The Externalizable interface extends Serializable and provides two methods: writeExternal(ObjectOutput out) and readExternal(ObjectInput in). Implementing this interface provides full control over the serialization and deserialization process. Unlike Serializable, the default serialization mechanism is bypassed. This requires you to handle all aspects of saving and restoring the object's state. An important point is that when deserializing an Externalizable object, the no-arg constructor is called first. Therefore, it's crucial to provide a public no-argument constructor in your Externalizable class.

Real-Life Use Case

Consider a scenario where you are serializing objects that contain sensitive information like passwords or API keys. Using Externalizable, you can selectively choose not to serialize those sensitive fields. Instead, you might encrypt them before serialization or retrieve them from a secure source after deserialization. Another use case is versioning. If the structure of your class changes significantly between versions, Externalizable allows you to handle the migration of data from older versions to newer versions gracefully during deserialization.

Best Practices

  • Always provide a public no-arg constructor: This is mandatory for Externalizable.
  • Handle versioning carefully: When the class structure changes, ensure readExternal can handle older serialized formats.
  • Consider security: Don't serialize sensitive data directly. Encrypt or exclude it.
  • Document your serialization format: Clearly document the format of the data written by writeExternal to ensure compatibility.

Interview Tip

During interviews, be prepared to explain the difference between Serializable and Externalizable. Highlight the advantages and disadvantages of each, focusing on the control Externalizable provides and the requirement for a no-arg constructor. Be ready to discuss scenarios where Externalizable would be the preferred choice due to security or versioning needs.

When to Use Externalizable

Use Externalizable when:

  • You need to control the serialization process precisely.
  • You want to exclude certain fields from serialization for security or performance reasons.
  • You need to handle versioning and compatibility with older serialized objects.
  • You want to optimize the serialization process for performance.

Memory Footprint

Externalizable can potentially reduce the memory footprint of serialized data because you explicitly choose which fields to serialize. By excluding unnecessary fields, you reduce the size of the serialized representation. However, the manual implementation of writeExternal and readExternal adds to the code size.

Alternatives

  • Serializable: Provides a simpler default serialization mechanism.
  • Custom Serialization with writeObject and readObject: Offers a middle ground between Serializable and Externalizable, allowing you to customize the serialization process without implementing the entire serialization logic yourself.
  • JSON or other external formats: Allows you to serialize objects into formats like JSON or XML, which can be more portable and human-readable.

Pros of Externalizable

  • Full control: Provides complete control over the serialization and deserialization process.
  • Security: Allows you to exclude sensitive data from serialization.
  • Versioning: Simplifies handling changes in class structure across different versions.
  • Performance: Enables optimization by selecting only necessary data for serialization.

Cons of Externalizable

  • Complexity: Requires manual implementation of serialization and deserialization logic.
  • Error-prone: Increased risk of errors due to manual implementation.
  • Maintenance: Requires more maintenance due to explicit serialization code.
  • No-arg constructor requirement: A public no-arg constructor is mandatory, which might not always be desirable.

FAQ

  • Why do I need a no-arg constructor for Externalizable?

    When deserializing an Externalizable object, the JVM first creates an instance of the class using its public no-argument constructor. Then, it calls the readExternal method to populate the object's fields. If a no-arg constructor is not available or not public, deserialization will fail.
  • What happens if I don't read or write all the fields in readExternal/writeExternal?

    If you don't write a field in writeExternal, its value won't be serialized. During deserialization, if you don't read it in readExternal, it will retain its default value (null for objects, 0 for int, etc.) after the object is deserialized. This behavior is intended and allows you to selectively exclude fields or provide default values.
  • How does Externalizable handle inheritance?

    If a class inherits from another class that implements Externalizable, the subclass is responsible for serializing and deserializing the state of its superclass. The subclass's writeExternal method should call the superclass's writeExternal method first, and similarly, the subclass's readExternal method should call the superclass's readExternal method first.