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.