ZetCode

Java Serializable Interface

Last modified: April 16, 2025

The java.io.Serializable interface is a marker interface that enables object serialization in Java. Serialization converts objects into byte streams for storage or transmission. Deserialization reconstructs objects from these byte streams.

Classes implement Serializable to indicate their instances can be serialized. The interface has no methods to implement. Serialization handles primitive types and object graphs automatically. It preserves object references and circular references.

Serializable Interface Overview

Serialization requires implementing the Serializable interface. The process uses ObjectOutputStream and ObjectInputStream. Transient fields are excluded from serialization. Static fields are never serialized.

public interface Serializable {
    // Marker interface - no methods
}

The code above shows the simple declaration of Serializable. Despite being empty, it enables powerful serialization capabilities. The JVM handles all serialization mechanics automatically when this interface is implemented.

Basic Serialization Example

This example demonstrates basic serialization of a simple object. The Person class implements Serializable. We serialize to a file and then deserialize back to an object.

Main.java
import java.io.*;

class Person implements Serializable {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

public class Main {
    public static void main(String[] args) {
        Person person = new Person("John Doe", 30);
        
        // Serialization
        try (ObjectOutputStream oos = 
                new ObjectOutputStream(new FileOutputStream("person.ser"))) {
            oos.writeObject(person);
            System.out.println("Serialized: " + person);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // Deserialization
        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 example shows complete serialization and deserialization of a Person object. The serialized data is written to "person.ser". After deserialization, the object has the same state as before serialization. Note the casting required when reading the object back.

Transient Fields Example

The transient keyword excludes fields from serialization. This is useful for sensitive data or fields that shouldn't persist. Transient fields are set to default values during deserialization.

Main.java
import java.io.*;

class Account implements Serializable {
    private String username;
    private transient String password; // Won't be serialized
    private double balance;
    
    public Account(String username, String password, double balance) {
        this.username = username;
        this.password = password;
        this.balance = balance;
    }
    
    @Override
    public String toString() {
        return "Account{username='" + username + "', password='" + 
            (password == null ? "null" : "****") + "', balance=" + balance + "}";
    }
}

public class Main {
    public static void main(String[] args) {
        Account account = new Account("johndoe", "secret123", 1000.50);
        
        // Serialize
        try (ObjectOutputStream oos = 
                new ObjectOutputStream(new FileOutputStream("account.ser"))) {
            oos.writeObject(account);
            System.out.println("Serialized: " + account);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // Deserialize
        try (ObjectInputStream ois = 
                new ObjectInputStream(new FileInputStream("account.ser"))) {
            Account deserialized = (Account) ois.readObject();
            System.out.println("Deserialized: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

This example demonstrates how transient fields behave during serialization. The password field is excluded from serialization. When deserialized, the password field is null. This protects sensitive data from being stored in serialized form.

Custom Serialization with readObject and writeObject

For more control over serialization, classes can implement custom methods. writeObject and readObject allow custom serialization logic. These methods must be private and have specific signatures.

Main.java
import java.io.*;

class Employee implements Serializable {
    private String name;
    private transient double salary; // Custom serialization
    
    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
    
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject(); // Serialize non-transient fields
        oos.writeDouble(salary * 1.1); // Custom serialization logic
    }
    
    private void readObject(ObjectInputStream ois) 
            throws IOException, ClassNotFoundException {
        ois.defaultReadObject(); // Deserialize non-transient fields
        this.salary = ois.readDouble() / 1.1; // Custom deserialization
    }
    
    @Override
    public String toString() {
        return "Employee{name='" + name + "', salary=" + salary + "}";
    }
}

public class Main {
    public static void main(String[] args) {
        Employee emp = new Employee("Alice Smith", 50000);
        
        // Serialize
        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();
        }
    }
}

This example shows custom serialization for a transient field. The salary is marked transient but still serialized using custom logic. During serialization, the salary is increased by 10%. During deserialization, it's decreased by 10%. This demonstrates how to implement custom serialization logic.

SerialVersionUID for Version Control

serialVersionUID is a version control mechanism for serialized classes. It ensures compatibility between serialized objects and class versions. If not defined, the JVM generates one based on class structure.

Main.java
import java.io.*;

class Product implements Serializable {
    private static final long serialVersionUID = 1L; // Version identifier
    private String name;
    private double price;
    
    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }
    
    @Override
    public String toString() {
        return "Product{name='" + name + "', price=" + price + "}";
    }
}

public class Main {
    public static void main(String[] args) {
        Product product = new Product("Laptop", 999.99);
        
        // Serialize
        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();
        }
    }
}

This example demonstrates using serialVersionUID for version control. The constant helps maintain compatibility if the class changes. If the UID differs during deserialization, an InvalidClassException occurs. Always explicitly declare serialVersionUID for important serializable classes.

Inheritance and Serialization

Serialization behavior with inheritance requires special consideration. If a superclass is serializable, its fields are serialized automatically. If not, its fields must be handled manually during serialization.

Main.java
import java.io.*;

class Address {
    private String city;
    private String country;
    
    public Address(String city, String country) {
        this.city = city;
        this.country = country;
    }
    
    @Override
    public String toString() {
        return city + ", " + country;
    }
}

class Customer extends Address implements Serializable {
    private String name;
    private transient Address address; // Non-serializable superclass
    
    public Customer(String name, String city, String country) {
        super(city, country);
        this.name = name;
        this.address = new Address(city, country);
    }
    
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        oos.writeObject(address.toString()); // Serialize superclass state
    }
    
    private void readObject(ObjectInputStream ois) 
            throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
        String addrStr = (String) ois.readObject();
        String[] parts = addrStr.split(", ");
        this.address = new Address(parts[0], parts[1]);
    }
    
    @Override
    public String toString() {
        return "Customer{name='" + name + "', address=" + address + "}";
    }
}

public class Main {
    public static void main(String[] args) {
        Customer customer = new Customer("Bob", "New York", "USA");
        
        // Serialize
        try (ObjectOutputStream oos = 
                new ObjectOutputStream(new FileOutputStream("customer.ser"))) {
            oos.writeObject(customer);
            System.out.println("Serialized: " + customer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // Deserialize
        try (ObjectInputStream ois = 
                new ObjectInputStream(new FileInputStream("customer.ser"))) {
            Customer deserialized = (Customer) ois.readObject();
            System.out.println("Deserialized: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

This example shows handling inheritance with serialization. The Address superclass isn't serializable, so we handle it manually. The Customer class implements custom serialization for the superclass fields. This ensures all necessary data is properly serialized and deserialized.

Serializing Collections

Java collections can be serialized if their elements are serializable. This includes ArrayList, HashMap, and other standard collections. The entire collection structure is preserved during serialization.

Main.java
import java.io.*;
import java.util.ArrayList;
import java.util.List;

class Student implements Serializable {
    private String name;
    private int id;
    
    public Student(String name, int id) {
        this.name = name;
        this.id = id;
    }
    
    @Override
    public String toString() {
        return "Student{name='" + name + "', id=" + id + "}";
    }
}

public class Main {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("Alice", 101));
        students.add(new Student("Bob", 102));
        students.add(new Student("Charlie", 103));
        
        // Serialize
        try (ObjectOutputStream oos = 
                new ObjectOutputStream(new FileOutputStream("students.ser"))) {
            oos.writeObject(students);
            System.out.println("Serialized: " + students);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // Deserialize
        try (ObjectInputStream ois = 
                new ObjectInputStream(new FileInputStream("students.ser"))) {
            @SuppressWarnings("unchecked")
            List<Student> deserialized = (List<Student>) ois.readObject();
            System.out.println("Deserialized: " + deserialized);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

This example demonstrates serializing an ArrayList of Student objects. The entire collection structure is preserved. Each element must implement Serializable. The deserialized collection is identical to the original, maintaining all elements and order.

Source

Java Serializable Interface Documentation

In this article, we've covered the essential aspects of the Java Serializable interface. Understanding serialization is crucial for persisting objects and transmitting them across networks 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.