One disadvantage of storing objects in memory, during program execution, is that those objects only exist until the program terminates, at which point their contents are lost. Wouldn’t it be nice if somehow the state of interesting objects could be saved and later restored, so that the program could maintain its state over multiple program invocations? Or even to convey the state of those objects to another program — even on another machine?

Well, you can. This is called Object Persistence, and it’s achieved in Java through a process called serialization.

Object Streams & Serialization

Here’s the relevant class hierarchy for object streams:

(Package java.io)

Object
    InputStream
        ObjectInputStream
    OutputStream
        ObjectOutputStream
Serializable
    Externalizable
ObjectInputValidation

Classes ObjectOutputStream and ObjectInputStream

Serialization is achieved using the classes ObjectOutputStream and ObjectInputStream.

However, a class must implement the Serializable interface to be successfully serialized. (This is not the default for security reasons.) The Serializable interface is a ‘marker interface’ — that is, it has no methods — so no other changes need to be made to your classes.

To serialize your class out to an ObjectOutputStream, you call that stream’s writeObject() method. Conversely, when you wish to serialize in from an ObjectInputStream, you call that stream’s readObject() method.

Here is how you can serialize out to a file. First, let’s create some classes to serialize:

package serialization;

import java.io.Serializable;

public class Person
    implements Serializable
{
    public Person(String name, int age)
    {
        m_name = name;
        m_age  = age;
    }
    
    public String getName()
    {
        return m_name;
    }
    
    public int getAge()
    {
        return m_age;
    }
    
    public String toString()
    {
        String s = getClass().getName();
        s += ":Name=" + m_name + ", Age=" + m_age;
        return s;
    }
    
    /////// Data ////////
    private String  m_name;
    private int     m_age;
}
package serialization;

public class Employee
    extends Person
{
    public Employee(String name,   int  age, 
                    double salary, long id)
    {
        super(name, age);
        m_salary = salary;
        m_id = id;
    }
    
    public double getSalary()
    {
        return m_salary;
    }
    
    public long getId()
    {
        return m_id;
    }
    
    public String toString()
    {
        String s = super.toString();
        s += ", Salary=" + m_salary + ", ID=" + m_id;
        return s;
    }
    
    
    /////// Data ///////
    private double m_salary;
    private long   m_id;
}
package serialization;

import java.util.Vector;

public class Manager
    extends Employee
{
    public Manager(String name,   int  age,
                   double salary, long id)
    {
        super(name, age, salary, id);
    }
    
    public void addEmployee(Employee emp)
    {
        m_directReports.addElement(emp);
    }

    public void removeEmployee(Employee emp)
    {
        m_directReports.removeElement(emp);
    }
    
    ///// Data ////
    private Vector m_directReports = new Vector();
}

Here’s how you serialize objects of these types out to a file:

package serialization;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SimpleSerializeOut
{
    public static void main(String[] args)
    {
        Manager bill = new Manager("Bill Gates", 47,
                                   100000000000.00, 1);
        Employee bob = new Employee("Bob Dole", 69,
                                    100000.00, 345);
        ObjectOutputStream out = null;
        try
        {
            out = new ObjectOutputStream(
                          new FileOutputStream(
                                   "employees.dat"));
            out.writeObject(bob);
            out.writeObject(bill);
            System.out.println("Serialization successful.");
        }
        catch(IOException e)
        {
            e.printStackTrace();
            System.out.println("Serialization failed.");
        }
        finally
        {
            if (out != null)
            {
                try
                {
                    out.close();
                }
                catch(IOException e)
                {}
            }
        }
    }
}

And here’s how you serialize them back in again, from the file:

package serialization;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class SimpleSerializeIn
{
    public static void main(String[] args)
    {
        Employee emp;
        Manager  mgr;
        
        ObjectInputStream in = null;
        boolean failed = true;
        try
        {
            in = new ObjectInputStream(
                          new FileInputStream(
                                   "employees.dat"));
            emp = (Employee)in.readObject();
            System.out.println("emp: " + emp);
            mgr = (Manager)in.readObject();
            System.out.println("mgr: " + mgr);
            System.out.println("Serialization successful.");
            failed = false;
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        catch(ClassNotFoundException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (in != null)
            {
                try
                {
                    in.close();
                }
                catch(IOException e)
                {}
            }
            if (failed)
                System.err.println("Serialization failed.");
        }
    }
}

This latter program outputs the following:

emp: serialization.Employee:Name=Bob Dole, Age=69, Salary=100000.0, ID=345
mgr: serialization.Manager:Name=Bill Gates, Age=47, Salary=1.0E11, ID=1
Serialization successful.

which shows that it worked!

Serializing Families of Classes

A number of issues arise when you attempt to serialize a related set of class instances:

  • All of the classes must be marked as Serializable
  • If the same object is referenced from more than one place, that object should still be serialized only once
  • There should be no situation where unlimited recursion occurs

The really nice thing about serialization is that the second and third items are taken care of automatically.

Let’s take our previous example of simple serialization, and modify the classes somewhat, to emphasize what happens when there are multiple references to the same object:

package serialization;

import java.io.Serializable;

public class Person
    implements Serializable
{
    public Person(String name, int age)
    {
        m_name = name;
        m_age  = age;
    }
    
    public String getName()
    {
        return m_name;
    }
    
    public int getAge()
    {
        return m_age;
    }
    
    public String toString()
    {
        String s = getClass().getName();
        // Strip off package name
        int index = s.lastIndexOf('.');
        if (index != -1)
            s = s.substring(index+1, s.length());
            
        s += ":Hashcode=@" + Integer.toHexString(hashCode()) + 
             ", Name=" + m_name + ", Age=" + m_age;
        return s;
    }
    
    /////// Data ////////
    private String  m_name;
    private int     m_age;
}
package serialization;

public class Employee
    extends Person
{
    public Employee(String name, int  age, 
                    double salary)
    {
        super(name, age);
        m_salary = salary;
    }
    
    public double getSalary()
    {
        return m_salary;
    }
    
    public void setManager(Manager mgr)
    {
        m_manager = mgr;
    }
    
    public Manager getManager()
    {
        return m_manager;
    }
    
    public String toString()
    {
        String s = super.toString();
        s += ", Salary=" + m_salary;
        return s;
    }
    
    /////// Data ///////
    private double  m_salary;
    private Manager m_manager;
}
package serialization;

import java.util.Enumeration;
import java.util.Vector;

public class Manager
    extends Employee
{
    public Manager(String name, int  age,
                   double salary)
    {
        super(name, age, salary);
    }
    
    public void setSecretary(Employee sec)
    {
        m_secretary = sec;
    }
    
    public Employee getSecretary()
    {
        return m_secretary;
    }
    
    public void addDirectReport(Employee emp)
    {
        m_directReports.addElement(emp);
    }

    public void removeDirectReport(Employee emp)
    {
        m_directReports.removeElement(emp);
    }
    
    public int getDirectReportsCount()
    {
        return m_directReports.size();
    }
    
    public Enumeration getDirectReports()
    {
        return m_directReports.elements();
    }
    
    public String toString()
    {
        String s = super.toString();
        s += ", Secretary=[" + m_secretary + "]";
        return s;
    }
    
    ///// Data ////
    private Vector   m_directReports = new Vector();
    private Employee m_secretary;
}

And a new Organization class:

package serialization;

import java.io.Serializable;
import java.util.Enumeration;
import java.util.Vector;

public class Organization
    implements Serializable
{
    public Organization(String name)
    {
        m_name = name;
    }
    
    public String getName()
    {
        return m_name;
    }
    
    public void setPresident(Manager pres)
    {
        m_president = pres;
    }
    
    public Manager getPresident()
    {
        return m_president;
    }
    
    public void addEmployee(Employee emp)
    {
        m_employees.addElement(emp);
    }

    public void removeEmployee(Employee emp)
    {
        m_employees.removeElement(emp);
    }
    
    public int getEmployeeCount()
    {
        return m_employees.size();
    }
    
    public Enumeration getEmployees()
    {
        return m_employees.elements();
    }
    
    public String toString()
    {
        String s = getName() + " has " +
                   getEmployeeCount() + " employees:\n";
        //Create organization chart, starting at president
        s += toString(m_president, 0);
        return s;
    }
    
    private static String toString(Employee emp, 
                                   int indentLevel)
    {
        String indent = "";
        for (int i = 0; i < indentLevel; i++)
            indent += "  ";
        String s = indent;
        s += emp.toString() + "\n";
        if (emp instanceof Manager)
        {
            Manager mgr = (Manager)emp;
            Enumeration en = mgr.getDirectReports();
            while (en.hasMoreElements())
            {
                Employee e = (Employee)en.nextElement();
                s += toString(e, indentLevel + 1);
            }
        }
        return s;
    }
    
    //// Data ////
    private String  m_name;
    private Manager m_president;
    private Vector  m_employees = new Vector();
}

I modified the classes to add a reference for each Employee‘s Manager, and each Manager‘s secretary. I augmented the Person class’s toString() method to show more specifics about the object (including its hashcode), and I removed the m_id field from Employee to simplify the output. Finally, I added a new class, Organization, which has references to each of its Employees, and contains a toString() method which is capable of returning an “org chart”.

Here’s the modified program to serialize out the family of related objects:

package serialization;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Enumeration;

public class FamilySerializeOut
{
    public static void main(String[] args)
    {
        Organization org = createOrganization();
        System.out.println("Org Chart:\n" + org);
        
        // Make asarray of all the organization's employees...
        Employee[] emps = new Employee[org.getEmployeeCount()];
        System.out.println("emps:");
        Enumeration en = org.getEmployees();
        for (int emp = 0; en.hasMoreElements(); emp++)
        {
            emps[emp] = (Employee)en.nextElement();
            System.out.println("[" + emp + "]:" + emps[emp]);
        }
        
        ObjectOutputStream out = null;
        boolean failed = true;
        try
        {
            out = new ObjectOutputStream(
                          new FileOutputStream(
                                   "org.dat"));
            // Serialize out the organization
            out.writeObject(org);
            // Serialize out the array of employees
            out.writeObject(emps);
            
            failed = false;
            System.out.println("Serialization successful.");
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (out != null)
            {
                try
                {
                    out.close();
                }
                catch(IOException e)
                {}
            }
            if (failed)
                System.out.println("Serialization failed.");
        }
    }
    
    private static Organization createOrganization()
    {
        Organization org = new Organization("Microsquish");
        Manager billg = createManager(org, null, null,
                                      "Bill Gates", 47,
                                      100000000000.00);
        org.setPresident(billg);
        
        Employee bettyb = createEmployee(org, billg,
                                         "Betty Boop", 56,
                                         36000.00);
        billg.setSecretary(bettyb);
        
        createSalesOrg(org);
        createEngineeringOrg(org, bettyb);
        
        return org;
    }
    
    private static Manager createSalesOrg(Organization org)
    {
        Manager elvisp = createManager(
                                    org, 
                                    org.getPresident(), 
                                    null,
                                    "Elvis Presley", 57,
                                    2000000.00);
        Employee oliveo = createEmployee(
                                    org, elvisp,
                                    "Olive Oyl", 48,
                                    32000.00);
        elvisp.setSecretary(oliveo);
        
        createEmployee(org, elvisp, 
                       "Prince", 25, 200000.00);
        createEmployee(org, elvisp,
                       "Madonna", 36, 400000.00);
        
        return elvisp;
    }
    
    private static Manager createEngineeringOrg(
                                    Organization org,
                                    Employee sec)
    {
        Manager carlp = createManager(
                                    org, 
                                    org.getPresident(), 
                                    sec,
                                    "Carl Perkins", 59,
                                    2000000.00);       
        createEmployee(org, carlp, 
                       "Porky Pig", 42, 20000.00);
        createEmployee(org, carlp,
                       "Taz Devil", 40, 40000.00);
        
        return carlp;
    }
    
    private static Manager createManager(
                                    Organization org, 
                                    Manager boss,
                                    Employee sec,
                                    String name, int age,
                                    double salary)
    {
        Manager mgr = new Manager(name, age, salary);
        doHousekeeping(mgr, org, boss);
        if (sec != null)
            mgr.setSecretary(sec);
        
        return mgr;
    }
    
    private static Employee createEmployee(
                                    Organization org,
                                    Manager boss,
                                    String name, int age,
                                    double salary)
    {
        Employee emp = new Employee(name, age, salary);
        doHousekeeping(emp, org, boss);
        return emp;
    }
    
    private static void doHousekeeping(
                                Employee emp,
                                Organization org, 
                                Manager boss)
    {
        org.addEmployee(emp);
        if (boss != null)
        {
            boss.addDirectReport(emp);
            emp.setManager(boss);
        }
    }
}

which adds a number of methods to create an Organization, and populate it with Employees and Managers and secretaries, and uses the Organization‘s toString() method to print out the entire “org chart”. Then, to show that the handling of multiple references works across calls to writeObject(), it creates an array of Employees and populates it with references to all the organization’s employees. In the process, it prints out the contents of the array. Finally, it serializes out the organization and then the array of employees.

Here’s what it outputs on my machine (note the hashcode values — each one identifies a unique object):

Org Chart:
Microsquish has 9 employees:
Manager:Hashcode=@277e69, Name=Bill Gates, Age=47, Salary=1.0E11, Secretary=[Employee:Hashcode=@277e64, Name=Betty Boop, Age=56, Salary=36000.0]
  Employee:Hashcode=@277e64, Name=Betty Boop, Age=56, Salary=36000.0
  Manager:Hashcode=@277e61, Name=Elvis Presley, Age=57, Salary=2000000.0, Secretary=[Employee:Hashcode=@277e5c, Name=Olive Oyl, Age=48, Salary=32000.0]
    Employee:Hashcode=@277e5c, Name=Olive Oyl, Age=48, Salary=32000.0
    Employee:Hashcode=@277e59, Name=Prince, Age=25, Salary=200000.0
    Employee:Hashcode=@277e56, Name=Madonna, Age=36, Salary=400000.0
  Manager:Hashcode=@277e53, Name=Carl Perkins, Age=59, Salary=2000000.0, Secretary=[Employee:Hashcode=@277e64, Name=Betty Boop, Age=56, Salary=36000.0]
    Employee:Hashcode=@277e4e, Name=Porky Pig, Age=42, Salary=20000.0
    Employee:Hashcode=@277e4b, Name=Taz Devil, Age=40, Salary=40000.0

emps:
[0]:Manager:Hashcode=@277e69, Name=Bill Gates, Age=47, Salary=1.0E11, Secretary=[Employee:Hashcode=@277e64, Name=Betty Boop, Age=56, Salary=36000.0]
[1]:Employee:Hashcode=@277e64, Name=Betty Boop, Age=56, Salary=36000.0
[2]:Manager:Hashcode=@277e61, Name=Elvis Presley, Age=57, Salary=2000000.0, Secretary=[Employee:Hashcode=@277e5c, Name=Olive Oyl, Age=48, Salary=32000.0]
[3]:Employee:Hashcode=@277e5c, Name=Olive Oyl, Age=48, Salary=32000.0
[4]:Employee:Hashcode=@277e59, Name=Prince, Age=25, Salary=200000.0
[5]:Employee:Hashcode=@277e56, Name=Madonna, Age=36, Salary=400000.0
[6]:Manager:Hashcode=@277e53, Name=Carl Perkins, Age=59, Salary=2000000.0, Secretary=[Employee:Hashcode=@277e64, Name=Betty Boop, Age=56, Salary=36000.0]
[7]:Employee:Hashcode=@277e4e, Name=Porky Pig, Age=42, Salary=20000.0
[8]:Employee:Hashcode=@277e4b, Name=Taz Devil, Age=40, Salary=40000.0
Serialization successful.

Now, let’s serialize the results back in from the file:

package serialization;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class FamilySerializeIn
{
    public static void main(String[] args)
    {
        ObjectInputStream in = null;
        boolean failed = true;
        try
        {
            in = new ObjectInputStream(
                          new FileInputStream(
                                   "org.dat"));
            Organization org = (Organization)in.readObject();
            System.out.println("Org Chart:\n" + org);
            Employee[] emps = (Employee[])in.readObject();
            System.out.println("emps:");
            for (int emp = 0; emp < emps.length; emp++)
                System.out.println("[" + emp + "]:" + emps[emp]);
            System.out.println("Serialization successful.");
            failed = false;
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        catch(ClassNotFoundException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (in != null)
            {
                try
                {
                    in.close();
                }
                catch(IOException e)
                {}
            }
            if (failed)
                System.err.println("Serialization failed.");
        }
    }
}

Note how little had to be changed!

This outputs, on my machine (again, note the hashcode values):

Org Chart:
Microsquish has 9 employees:
Manager:Hashcode=@27812d, Name=Bill Gates, Age=47, Salary=1.0E11, Secretary=[Employee:Hashcode=@278127, Name=Betty Boop, Age=56, Salary=36000.0]
  Employee:Hashcode=@278127, Name=Betty Boop, Age=56, Salary=36000.0
  Manager:Hashcode=@278123, Name=Elvis Presley, Age=57, Salary=2000000.0, Secretary=[Employee:Hashcode=@27811d, Name=Olive Oyl, Age=48, Salary=32000.0]
    Employee:Hashcode=@27811d, Name=Olive Oyl, Age=48, Salary=32000.0
    Employee:Hashcode=@278119, Name=Prince, Age=25, Salary=200000.0
    Employee:Hashcode=@278115, Name=Madonna, Age=36, Salary=400000.0
  Manager:Hashcode=@278111, Name=Carl Perkins, Age=59, Salary=2000000.0, Secretary=[Employee:Hashcode=@278127, Name=Betty Boop, Age=56, Salary=36000.0]
    Employee:Hashcode=@27810b, Name=Porky Pig, Age=42, Salary=20000.0
    Employee:Hashcode=@278107, Name=Taz Devil, Age=40, Salary=40000.0

emps:
[0]:Manager:Hashcode=@27812d, Name=Bill Gates, Age=47, Salary=1.0E11, Secretary=[Employee:Hashcode=@278127, Name=Betty Boop, Age=56, Salary=36000.0]
[1]:Employee:Hashcode=@278127, Name=Betty Boop, Age=56, Salary=36000.0
[2]:Manager:Hashcode=@278123, Name=Elvis Presley, Age=57, Salary=2000000.0, Secretary=[Employee:Hashcode=@27811d, Name=Olive Oyl, Age=48, Salary=32000.0]
[3]:Employee:Hashcode=@27811d, Name=Olive Oyl, Age=48, Salary=32000.0
[4]:Employee:Hashcode=@278119, Name=Prince, Age=25, Salary=200000.0
[5]:Employee:Hashcode=@278115, Name=Madonna, Age=36, Salary=400000.0
[6]:Manager:Hashcode=@278111, Name=Carl Perkins, Age=59, Salary=2000000.0, Secretary=[Employee:Hashcode=@278127, Name=Betty Boop, Age=56, Salary=36000.0]
[7]:Employee:Hashcode=@27810b, Name=Porky Pig, Age=42, Salary=20000.0
[8]:Employee:Hashcode=@278107, Name=Taz Devil, Age=40, Salary=40000.0
Serialization successful.

This shows that each unique object was serialized out once, and then serialized back in only once.

Serialization & Class Versioning

When we serialize data, we must be careful about making changes to the classes and expecting the serialization to continue working.

Here, we explain the problem and then show how it’s addressed.

A Simple Application

Consider the following classes:

package serialization;

import java.io.Serializable;

public class Address implements Serializable
{
    public Address(String name, String address)
    {
        m_name = name;
        m_address = address;
    }
    
    public String getName()
    {
        return m_name;
    }
    
    public String getAddress()
    {
        return m_address;
    }
    
    ///// Private data /////
    private String m_name;
    private String m_address;
}
package serialization;

import java.io.Serializable;
import java.util.Enumeration;
import java.util.Hashtable;

public class AddressBook implements Serializable
{
    public Address lookup(String name)
    {
        return (Address) m_table.get(name);
    }
    
    public Address add(String name, String address)
    {
        return (Address) m_table.put(name,
                                     new Address(name,
                                                 address));
    }
    
    public int size()
    {
        return m_table.size();
    }
    
    public Enumeration addresses()
    {
        return m_table.elements();
    }
    
    //// Private data ////
    private Hashtable m_table = new Hashtable();
}

Now, let’s write a program to populate an AddressBook with Addresses and then serialize the results out:

package serialization;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializeOutAddressBook
{
    public static void main(String[] args)
    {
        AddressBook book = new AddressBook();
        book.add("Bill Gates", "1000 High-on-the-Hog Drive");
        book.add("Scott McNealy", "4000 Java Circle");
        book.add("Linus Torvalds", "300 Linux Lane");
        
        FileOutputStream fout = null;
        ObjectOutputStream out = null;
        try
        {
            fout = new FileOutputStream("addrbook.ser");
            out = new ObjectOutputStream(fout);
            out.writeObject(book);
            
            System.out.println("Serialization successful!");
        }
        catch(IOException ex)
        {
            ex.printStackTrace();
        }
        finally
        {
            try
            {
                if (out != null)
                    out.close();
                if (fout != null)
                    fout.close();
            }
            catch(IOException ex)
            {   /* Do nothing */ }
        }
    }
}

Finally, let’s write a program to serialize the AddressBook back in, and display the Addresses it contains:

package serialization;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Enumeration;

public class SerializeInAddressBook
{
    public static void main(String[] args)
    {
        AddressBook book = null;
        FileInputStream fin = null;
        ObjectInputStream in = null;
        try
        {
            fin = new FileInputStream("addrbook.ser");
            in = new ObjectInputStream(fin);
            book = (AddressBook) in.readObject();
            
            System.out.println("Serialization successful!");
        
            Enumeration en = book.addresses();
            while (en.hasMoreElements())
            {
                Address addr = (Address) en.nextElement();
                System.out.println(
                    "Name: [" + addr.getName() + "], " +
                    "Address: [" + addr.getAddress() + "]");
            }
        }
        catch(ClassNotFoundException ex)
        {
            ex.printStackTrace();
        }
        catch(IOException ex)
        {
            ex.printStackTrace();
        }
        finally
        {
            try
            {
                if (in != null)
                    in.close();
                if (fin != null)
                    fin.close();
            }
            catch(IOException ex)
            {   /* Do nothing */ }
        }
    }
}

which outputs the following:

Serialization successful!
Name: [Linus Torvalds], Address: [300 Linux Lane]
Name: [Scott McNealy], Address: [4000 Java Circle]
Name: [Bill Gates], Address: [1000 High-on-the-Hog Drive]

Changing a Class

If you simply serialize out with no special handling, then, by default, ObjectOutputStream will store the fully qualified name of the class being serialized, plus a version number.

This unique version number is computed for the class by applying the Secure Hash Algorithm (SHA) to the name of the class, its interfaces, fields, and methods. If any such changes are made to the class, the version number will change. This means that a class can become incompatible with its serialized versions.

For example, I added an extra field (a phone number) and modified some methods in the Address class:

package serialization;

import java.io.Serializable;

public class Address implements Serializable
{
    public Address(String name, String address)
    {
        this(name, address, null);
    }
    
    public Address(String name, String address, String phone)
    {
        m_name = name;
        m_address = address;
        m_phone = phone;
    }
    
    public String getName()
    {
        return m_name;
    }
    
    public String getAddress()
    {
        return m_address;
    }
    
    public String getPhone()
    {
        return m_phone;
    }
    
    ///// Private data /////
    private String m_name;
    private String m_address;
    private String m_phone;
}

But when I try to serialize an older version back in, it gives me the following error, in my Java environment:

java.io.InvalidClassException: serialization.Address; 
      Local class not compatible: stream classdesc serialVersionUID=8364956740597978489 
                                       local class serialVersionUID=2153101615360274995
    at java.io.ObjectStreamClass.setClass(ObjectStreamClass.java:242)
    at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java:735)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:328)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:225)
    at java.io.ObjectInputStream.inputObject(ObjectInputStream.java:933)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:344)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:225)
    at java.util.Hashtable.readObject(Hashtable.java:499)
    at java.io.ObjectInputStream.inputObject(ObjectInputStream.java:1121)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:344)
    at java.io.ObjectInputStream.defaultReadObject(ObjectInputStream.java:474)
    at java.io.ObjectInputStream.inputObject(ObjectInputStream.java:1122)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:344)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:225)
    at serialization.SerializeInAddressBook.main(SerializeInAddressBook.java:19)
    at symantec.tools.debug.MainThread.run(Agent.java:48)

Specifying an Explicit Version

But we’d sometimes like to change the class in a way that should not disable the ability to serialize older versions in.

In order to do this, we have to specify the version number explicitly. 

You do this by specifying a field in your class:

static final long serialVersionUID = 8364956740597978489L;

The long numeric literal is the version number. Where did it come from?

The JDK supplies a program called serialver which can generate a field declaration similar to the above. Once we’ve used serialver to determine the version number for the original class, we can add this line to the modified class and try it again.

Voila! The serialization in now works!

Now we can augment our serialization programs to support the phone number:

package serialization;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializeOutAddressBook
{
    public static void main(String[] args)
    {
        AddressBook book = new AddressBook();
        book.add("Bill Gates", "1000 High-on-the-Hog Drive",
                                "981-786-5421");
        book.add("Scott McNealy", "4000 Java Circle");
        book.add("Linus Torvalds", "300 Linux Lane",
                                "653-876-2378");
        
        FileOutputStream fout = null;
        ObjectOutputStream out = null;
        try
        {
            fout = new FileOutputStream("addrbook2.ser");
            out = new ObjectOutputStream(fout);
            out.writeObject(book);
            
            System.out.println("Serialization successful!");
        }
        catch(IOException ex)
        {
            ex.printStackTrace();
        }
        finally
        {
            try
            {
                if (out != null)
                    out.close();
                if (fout != null)
                    fout.close();
            }
            catch(IOException ex)
            {   /* Do nothing */ }
        }
    }
}
package serialization;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Enumeration;

public class SerializeInAddressBook
{
    public static void main(String[] args)
    {
        AddressBook book = null;
        FileInputStream fin = null;
        ObjectInputStream in = null;
        try
        {
            Enumeration en = null;
            
            System.out.println("Serializing in addrbook2.ser...");
            
            fin = new FileInputStream("addrbook2.ser");
            in = new ObjectInputStream(fin);
            book = (AddressBook) in.readObject();
            
            System.out.println(
                    "Serialization of addrbook2.ser successful!");
        
            en = book.addresses();
            while (en.hasMoreElements())
            {
                Address addr = (Address) en.nextElement();
                System.out.println(
                    "Name: [" + addr.getName() + "], " +
                    "Address: [" + addr.getAddress() + "]" +
                    "Phone: [" + addr.getPhone() + "]");
            }
            
            in.close();
            
            System.out.println("Serializing in addrbook.ser...");
            
            fin = new FileInputStream("addrbook.ser");
            in = new ObjectInputStream(fin);
            book = (AddressBook) in.readObject();
            
            System.out.println(
                    "Serialization of addrbook.ser successful!");
            
            en = book.addresses();
            while (en.hasMoreElements())
            {
                Address addr = (Address) en.nextElement();
                System.out.println(
                    "Name: [" + addr.getName() + "], " +
                    "Address: [" + addr.getAddress() + "]" +
                    "Phone: [" + addr.getPhone() + "]");
            }
            
        }
        catch(ClassNotFoundException ex)
        {
            ex.printStackTrace();
        }
        catch(IOException ex)
        {
            ex.printStackTrace();
        }
        finally
        {
            try
            {
                if (in != null)
                    in.close();
                if (fin != null)
                    fin.close();
            }
            catch(IOException ex)
            {   /* Do nothing */ }
        }
    }
}

This now works correctly.

Custom Serialization & Security

Not every item in a class object can, or should be, serialized. Here are some of the issues:

  • Some objects are inherently specific to the program’s execution state, and would have no meaning elsewhere.For example, a FileDescriptor object would have no meaning outside of the program that was using it. Especially on another machine or operating system architecture!
    Another example is a GUI-based class that retains knowledge of its size based on a calculation previously performed using knowledge of local font sizes. Since font sizes vary from machine to machine, this piece of state would not be useful on another machine.
    Yet another example: A class that obtains some platform-specific information, such as is available from the system properties. This information should not be serialized out, even if it is only to be used on the same machine type. This is because things may change, even on the same machine: the JDK version may change; the user’s default directory may change, or even the operating system may change.
  • Some object state is sensitive, and should not be made available outside of its security domain.For example, a password should not typically be serialized, except in an encrypted form.
  • Some classes are constructed in a way that guarantees their validity. Serialization provides an opportunity to subvert such guarantees.
    For example, a Date object might be constructed in such a way as to make a date of 31 February 1999 impossible. However, if that object were serialized out to a file, and the file was subsequently tampered with, such an invalid date could be created as a result of serializing it back into memory.

For these reasons, classes are not Serializable by default. However, what if we wish a class to be Serializable, but it contains items with such problems?

There are two possible solutions:

  • We could prevent the sensitive items from being serialized out.
  • We could allow the sensitive items to be serialized out, but in some special form that does not present any of the above problems.

Preventing Serialization

If you wish an entire class not to be serializable, you can simply not have that class implement Serializable.

If you wish a specific field in a class not to be serialized, you can declare it as transient.

When you serialize out a class object, the writeObject() method will write out information about the class of the object, the signature of the class, and the values of any field that is not transient.

Consider the following example. Here’s a modified version of the Person class I used earlier (I’ve added setName() and setAge() methods):

package serialization;

import java.io.Serializable;

public class Person
    implements Serializable
{
    public Person(String name, int age)
    {
        m_name = name;
        m_age  = age;
    }
    
    public String getName()
    {
        return m_name;
    }
    
    public void setName(String name)
    {
        m_name = name;
    }
    
    public int getAge()
    {
        return m_age;
    }
    
    public void setAge(int age)
    {
        m_age = age;
    }
    
    public String toString()
    {
        String s = getClass().getName();
        // Strip off package name
        int index = s.lastIndexOf('.');
        if (index != -1)
            s = s.substring(index+1, s.length());
            
        s += ":Hashcode=@" + Integer.toHexString(hashCode()) + 
             ", Name=" + m_name + ", Age=" + m_age;
        return s;
    }
    
    /////// Data ////////
    private String  m_name;
    private int     m_age;
}

I’ve also introduced a new class, RecordedPerson:

package serialization;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public class RecordedPerson 
    extends Person
{
    // Constructor to create a recorded person and 
    // the associated record file
    public RecordedPerson(String name, int age, int id)
    {
        super(name, age);
        m_id = id;
        createRecordFile();
    }
    
    // Constructor to create a recorded person
    // and to open an already existing record file.
    public RecordedPerson(int id)
    {
        super(null, -1);
        m_id = id;
        setRecordFile();
        setBasicData();
    }
    
    // Reads the record from the record file
    // and returns it as a string.
    public String getRecord()
    {
        String data = "";
        BufferedReader in = null;
        try
        {
            in = new BufferedReader(
                         new FileReader(m_recordFile));
            while(true)
            {
                String line = in.readLine();
                if (line == null)
                    break;
                data += line + "\n";
            }
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (in != null)
            {
                try
                {
                    in.close();
                }
                catch(IOException e)
                {}
            }
        }
        return data;
    }
    
    // Adds a new item to the record file for this person
    public void addRecord(String item)
    {
        PrintWriter out = null;
        try
        {
            out = new PrintWriter(
                        new FileWriter(
                                m_recordFile.getAbsolutePath(),
                                true));
            out.println(item);
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (out != null)
                out.close();
        }
    }
    
    // Creates a new record file with name and age record
    // (If the record file already exists, assumes that it's valid)
    private void createRecordFile()
    {
        setRecordFile();
        if (!m_recordFile.exists())
        {
            // Create a new record file
            PrintWriter out = null;
            try
            {
                out = new PrintWriter(
                                new FileWriter(m_recordFile));
                out.println(getName() + "," + getAge());
            }
            catch(IOException e)
            {
                e.printStackTrace();
            }
            finally
            {
                if (out != null)
                    out.close();
            }
        }
        else
        {
            // Assume for simplicity that the name and age
            // actually correspond with the current ones.
        }
    }
    
    // Sets the record File object
    // If necessary, creates the subdirectory for record files.
    private void setRecordFile()
    {
        File records = new File("records");
        if (!records.exists())
            records.mkdir();    // Create directory
        m_recordFile = new File(records, "r" + m_id + ".rec");
    }
    
    // Reads the record file and extracts the name and age
    // and sets those values for the current recorded person.
    private void setBasicData()
    {
        // Extract the name and age from the record
        String record = getRecord();
        int commaIndex = record.indexOf(',');
        int eolIndex = record.indexOf('\n');
        String name = record.substring(0, commaIndex);
        String age = record.substring(commaIndex+1, eolIndex);
        // Set the name and age values
        setName(name);
        setAge(Integer.parseInt(age));
    }
    
    // Creates a string containing RecordedPerson information.
    public String toString()
    {
        String s = super.toString();
        s += ", ID=" + m_id + ", recordFile=" + m_recordFile;
        return s;
    }
    //// Data ////
    private int   m_id;
    private File  m_recordFile;
}

Here’s a program that serializes out a RecordedPerson object in the normal way:

package serialization;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class RecordedSerializeOut
{
    public static void main(String[] args)
    {
        RecordedPerson rc = new RecordedPerson(
                                    "Franco Limpone", 42, 666);
        System.out.println("rc: " + rc);                                    
        System.out.println(rc.getRecord());

        ObjectOutputStream out = null;
        try
        {
            out = new ObjectOutputStream(
                          new FileOutputStream(
                                   "recorded.dat"));
            out.writeObject(rc);
            System.out.println("Serialization successful.");
        }
        catch(IOException e)
        {
            e.printStackTrace();
            System.out.println("Serialization failed.");
        }
        finally
        {
            if (out != null)
            {
                try
                {
                    out.close();
                }
                catch(IOException e)
                {}
            }
        }
    }
}

It outputs the following on my machine:

rc: RecordedPerson:Hashcode=@277e21, Name=Franco Limpone, Age=42, ID=666, recordFile=records\r666.rec
Franco Limpone,42
Convicted of Assault & Battery, 14-Dec-1999

Notice that the record file is records\r666.rec, which is not a portable file specification.

If we serialize this back in using the following program:

package serialization;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class RecordedSerializeIn
{
    public static void main(String[] args)
    {
        ObjectInputStream in = null;
        boolean failed = true;
        try
        {
            in = new ObjectInputStream(
                          new FileInputStream(
                                   "recorded.dat"));
            RecordedPerson rc = (RecordedPerson)in.readObject();
            System.out.println("rc: " + rc);
            System.out.println(rc.getRecord());
            System.out.println("Serialization successful.");
            failed = false;
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        catch(ClassNotFoundException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (in != null)
            {
                try
                {
                    in.close();
                }
                catch(IOException e)
                {}
            }
            if (failed)
                System.err.println("Serialization failed.");
        }
    }
}

This program outputs the following on my machine (which is the same machine on which I serialized it out):

rc: RecordedPerson:Hashcode=@277fd2, Name=Franco Limpone, Age=42, ID=666, recordFile=records\r666.rec
Franco Limpone,42
Convicted of Assault & Battery, 14-Dec-1999

The recordFile is set to records\r666.rec, which means that the File object was successfully serialized out and then back in. However, ask yourself the following:

  • What would have happened if the serialization back in had occurred on another machine?
  • What would have happened if the program which did the serialization back in had a different default user directory from the program which did the serialization out?

Now, it turns out that the File class code ensures during serialization in that the separator (‘\’) is correctly set, so presumably the code would work on another machine (although I wouldn’t necessarily bet on it!).

So let’s say we don’t want to take the risk, and so we choose not to serialize the recordFile field. We can do this by merely changing one line in RecordedPerson:

private transient File  m_recordFile;

which makes no perceptable difference in the output of the program that serializes out. However, if we run the program that serializes back in, it outputs the following:

rc: RecordedPerson:Hashcode=@277f8d, Name=Franco Limpone, Age=42, ID=666, recordFile=null
Serialization failed.
java.lang.NullPointerException
    at java.io.FileInputStream.<init>(FileInputStream.java:75)
    at java.io.FileReader.<init>(FileReader.java:39)
    at serialization.RecordedPerson.getRecord(RecordedPerson.java:41)
    at serialization.RecordedSerializeIn.main(RecordedSerializeIn.java:20)
    at symantec.tools.debug.MainThread.run(Agent.java:48)

So, clearly, the recordFile field contents did not get serialized out, and its value was set to null on serialization back in.

What to do? We have to do some custom serialization

Custom Serialization

Java’s serialization mechanism provides a mechanism for classes to specify how they wish the serialization to proceed. You can:

  • supplement the default serialization, or
  • override it entirely

You can specify that a class perform custom serialization by defining methods with the signatures:

private void writeObject(ObjectOutputStream out)
     throws IOException

and:

private void readObject(ObjectInputStream in)
     throws IOException,
            ClassNotFoundException

Note: These methods must be declared private, and are not declared in any interface (interfaces can only declare public methods).

If you wish to supplement the default serialization, you must ensure that when you implement these methods, they call the default serialization methods:

private void writeObject(ObjectOutputStream out)
     throws IOException
{
    // ...
    out.defaultWriteObject();
    // ...
}
private void readObject(ObjectInputStream in)
     throws IOException,
            ClassNotFoundException
{
    // ...
    in.defaultReadObject();
    // ...
}

So let’s modify the RecordedPerson class to augment the default serialization on input:

package serialization;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.PrintWriter;

public class RecordedPerson 
    extends Person
{
    // Constructor to create a recorded person and 
    // the associated record file
    public RecordedPerson(String name, int age, int id)
    {
        super(name, age);
        m_id = id;
        createRecordFile();
    }
    
    // Constructor to create a recorded person
    // and to open an already existing record file.
    public RecordedPerson(int id)
    {
        super(null, -1);
        m_id = id;
        setRecordFile();
        setBasicData();
    }
    
    // Reads the record from the record file
    // and returns it as a string.
    public String getRecord()
    {
        String data = "";
        BufferedReader in = null;
        try
        {
            in = new BufferedReader(
                         new FileReader(m_recordFile));
            while(true)
            {
                String line = in.readLine();
                if (line == null)
                    break;
                data += line + "\n";
            }
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (in != null)
            {
                try
                {
                    in.close();
                }
                catch(IOException e)
                {}
            }
        }
        return data;
    }
    
    // Adds a new item to the record file for this person
    public void addRecord(String item)
    {
        PrintWriter out = null;
        try
        {
            out = new PrintWriter(
                        new FileWriter(
                                m_recordFile.getAbsolutePath(),
                                true));
            out.println(item);
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (out != null)
                out.close();
        }
    }
    
    // Creates a new record file with name and age record
    // (If the record file already exists, assumes that it's valid)
    private void createRecordFile()
    {
        setRecordFile();
        if (!m_recordFile.exists())
        {
            // Create a new record file
            PrintWriter out = null;
            try
            {
                out = new PrintWriter(
                                new FileWriter(m_recordFile));
                out.println(getName() + "," + getAge());
            }
            catch(IOException e)
            {
                e.printStackTrace();
            }
            finally
            {
                if (out != null)
                    out.close();
            }
        }
        else
        {
            // Assume for simplicity that the name and age
            // actually correspond with the current ones.
        }
    }
    
    // Sets the record File object
    // If necessary, creates the subdirectory for record files.
    private void setRecordFile()
    {
        File records = new File("records");
        if (!records.exists())
            records.mkdir();    // Create directory
        m_recordFile = new File(records, "r" + m_id + ".rec");
    }
    
    // Reads the record file and extracts the name and age
    // and sets those values for the current recorded person.
    private void setBasicData()
    {
        // Extract the name and age from the record
        String record = getRecord();
        int commaIndex = record.indexOf(',');
        int eolIndex = record.indexOf('\n');
        String name = record.substring(0, commaIndex);
        String age = record.substring(commaIndex+1, eolIndex);
        // Set the name and age values
        setName(name);
        setAge(Integer.parseInt(age));
    }
    
    // Creates a string containing RecordedPerson information.
    public String toString()
    {
        String s = super.toString();
        s += ", ID=" + m_id + ", recordFile=" + m_recordFile;
        return s;
    }
    
    private void readObject(ObjectInputStream in)
        throws IOException,
                ClassNotFoundException
    {
        in.defaultReadObject();
        // Ensure that the recordFile is properly set
        setRecordFile();
    }
    
    //// Data ////
    private int   m_id;
    private transient File  m_recordFile;
}

Which produces the proper output on serialization back in:

rc: RecordedPerson:Hashcode=@278019, Name=Franco Limpone, Age=42, ID=666, recordFile=records\r666.rec
Franco Limpone,42
Convicted of Assault & Battery, 14-Dec-1999
Adding Items to the Serialized Class

If desired, you can implement your writeObject() method in a way similar to:

private void writeObject(ObjectOutputStream out)
     throws IOException
{
    out.defaultWriteObject();
    out.writeObject(myObject);  // write out additional object
}

However, if you do this, then you must also do the equivalent when serializing back in:

private void readObject(ObjectInputStream in)
     throws IOException,
            ClassNotFoundException
{
    in.defaultReadObject();
    myObject = in.readObject(); // read in additional object
}

Advanced Serialization

The Externalizable Interface

If a class implements the Externalizable interface, the ObjectInputStream and ObjectOutputStream classes use that object’s readExternal() and writeExternal() methods to read and write its state during serialization.

The Externalizable interface extends from Serializable:

package java.io;

import java.io.ObjectOutput;
import java.io.ObjectInput;

public interface Externalizable extends java.io.Serializable {
    /**
     * The object implements the writeExternal method to save its contents
     * by calling the methods of DataOutput for its primitive values or
     * calling the writeObject method of ObjectOutput for objects, strings
     * and arrays.
     * @exception IOException Includes any I/O exceptions that may occur
     * @since     JDK1.1
     */
    void writeExternal(ObjectOutput out) throws IOException;

    /**
     * The object implements the readExternal method to restore its
     * contents by calling the methods of DataInput for primitive
     * types and readObject for objects, strings and arrays.  The
     * readExternal method must read the values in the same sequence
     * and with the same types as were written by writeExternal.
     * @exception ClassNotFoundException If the class for an object being
     *              restored cannot be found.
     * @since     JDK1.1
     */
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

By implementing Externalizable, a class can control whether the state of superclasses is stored in the stream, and exactly which fields are stored. Only the identity of an Externalized object is automatically saved by the stream; the class is responsible for writing and reading its contents, and must coordinate with its superclasses to do so.

Unlike Serializable, the Externalizable interface does not handle code versioning automatically — you must provide your own.

Because the state of an Externalizable object’s superclasses can be indirectly manipulated by the writeExternal() and readExternal() methods, which are public, you should use Externalizable with extreme caution to avoid security problems.

The ObjectInputValidation Interface

Sometimes you go to great pains to construct a set of objects correctly, usually by putting the appropriate code in the class constructor(s). However, because restoring a set of Serializable objects does not invoke their constructors, it is often desireable to ensure that the reconstructed object is indeed valid. The ObjectInputValidation interface provides such capabilities.

Here is what it looks like:

package java.io;

/**
 * Callback interface to allow validation of objects within a graph.
 * Allows an object to be called when a complete graph of objects has
 * been deserialized.
 */
public interface ObjectInputValidation {
    /**
     * Validates the object
     * @exception InvalidObjectException If the object cannot validate itself.
     * @since     JDK1.1
     */
    public void validateObject() throws InvalidObjectException;
}

ObjectInputStream uses a registration scheme to allow you to register an ObjectInputValidation object during serialization in.

For example, if we take the RecordedPerson class we used to do custom serialization earlier, and add to it support for validation, we get the following class, with highlighted changes:

package serialization;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InvalidObjectException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectInputValidation;
import java.io.PrintWriter;

public class RecordedPerson 
    extends Person
    implements ObjectInputValidation
{
    // Constructor to create a recorded person and 
    // the associated record file
    public RecordedPerson(String name, int age, int id)
    {
        super(name, age);
        m_id = id;
        createRecordFile();
    }
    
    // Constructor to create a recorded person
    // and to open an already existing record file.
    public RecordedPerson(int id)
    {
        super(null, -1);
        m_id = id;
        setRecordFile();
        setBasicData();
    }
    
    // Reads the record from the record file
    // and returns it as a string.
    public String getRecord()
    {
        String data = "";
        BufferedReader in = null;
        try
        {
            in = new BufferedReader(
                         new FileReader(m_recordFile));
            while(true)
            {
                String line = in.readLine();
                if (line == null)
                    break;
                data += line + "\n";
            }
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (in != null)
            {
                try
                {
                    in.close();
                }
                catch(IOException e)
                {}
            }
        }
        return data;
    }
    
    // Adds a new item to the record file for this person
    public void addRecord(String item)
    {
        PrintWriter out = null;
        try
        {
            out = new PrintWriter(
                        new FileWriter(
                                m_recordFile.getAbsolutePath(),
                                true));
            out.println(item);
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (out != null)
                out.close();
        }
    }
    
    // Creates a new record file with name and age record
    // (If the record file already exists, assumes that it's valid)
    private void createRecordFile()
    {
        setRecordFile();
        if (!m_recordFile.exists())
        {
            // Create a new record file
            PrintWriter out = null;
            try
            {
                out = new PrintWriter(
                                new FileWriter(m_recordFile));
                out.println(getName() + "," + getAge());
            }
            catch(IOException e)
            {
                e.printStackTrace();
            }
            finally
            {
                if (out != null)
                    out.close();
            }
        }
        else
        {
            // Assume for simplicity that the name and age
            // actually correspond with the current ones.
        }
    }
    
    // Sets the record File object
    // If necessary, creates the subdirectory for record files.
    private void setRecordFile()
    {
        File records = new File("records");
        if (!records.exists())
            records.mkdir();    // Create directory
        m_recordFile = new File(records, "r" + m_id + ".rec");
    }
    
    // Reads the record file and extracts the name and age
    // and sets those values for the current recorded person.
    private void setBasicData()
    {
        // Extract the name and age from the record
        String record = getRecord();
        int commaIndex = record.indexOf(',');
        int eolIndex = record.indexOf('\n');
        String name = record.substring(0, commaIndex);
        String age = record.substring(commaIndex+1, eolIndex);
        // Set the name and age values
        setName(name);
        setAge(Integer.parseInt(age));
    }
    
    // Creates a string containing RecordedPerson information.
    public String toString()
    {
        String s = super.toString();
        s += ", ID=" + m_id + ", recordFile=" + m_recordFile;
        return s;
    }
    
    private void readObject(ObjectInputStream in)
        throws IOException,
                ClassNotFoundException
    {
        in.registerValidation(this, 0);
        in.defaultReadObject();
        // Ensure that the recordFile is properly set
        setRecordFile();
    }
    
    public void validateObject() 
        throws InvalidObjectException
    {
        if (getAge() > 40)
            throw new InvalidObjectException("No persons over 40 allowed");
    }
    
    //// Data ////
    private int   m_id;
    private transient File  m_recordFile;
}

Now, when we attempt to serialize this class back in, we now get the following:

java.io.InvalidObjectException: No persons over 40 allowed
    at serialization.RecordedPerson.validateObject(RecordedPerson.java:175)
    at java.io.ObjectInputStream.doValidations(ObjectInputStream.java:548)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:437)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:225)
    at serialization.RecordedSerializeIn.main(RecordedSerializeIn.java:18)
    at symantec.tools.debug.MainThread.run(Agent.java:48)
Serialization failed.