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.
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.
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.
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.
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.
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.
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.
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
List all Java tutorials.