If you’re working with Spring, check out "REST With Spring":

>> CHECK OUT THE COURSE

1. Introduction

Serialization is the conversion of the state of an object into a byte stream; deserialization does the opposite. Stated differently, serialization is the conversion of a Java object into a static stream (sequence) of bytes which can then be saved to a database or transferred over a network.

2. Serialization and Deserialization

The serialization process is instance-independent, i.e. objects can be serialized on one platform and deserialized on another. Classes that are eligible for serialization need to implement a special marker interface Serializable.

Both ObjectInputStream and ObjectOutputStream are high level classes that extend java.io.InputStream and java.io.OutputStream respectively. ObjectOutputStream can write primitive types and graphs of objects to an OutputStream as a stream of bytes. These streams can subsequently be read using ObjectInputStream.

The most important method in ObjectOutputStream is:

public final void writeObject(Object o) throws IOException;

Which takes a serializable object and converts it into a sequence (stream) of bytes. Similarly, the most important method in ObjectInputStream is:

public final Object readObject() 
  throws IOException, ClassNotFoundException;

Which can read a stream of bytes and convert it back into a Java object. This can then be cast back to the original object.

Let’s illustrate serialization with a Person class. Note that static fields belong to a class (as opposed to an object) and are not serialized. Also, note that we can use the keyword transient to ignore class fields during serialization:

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    static String country = "ITALY";
    private int age;
    private String name;
    transient int height;

    // getters and setters
}

The test below shows an example of saving an object of type Person to a local file then read this value back in:

@Test 
public void whenSerializingAndDeserializing_ThenObjectIsTheSame() () 
  throws IOException, ClassNotFoundException { 
    Person person = new Person();
    person.setAge(20);
    person.setName("Joe");
    
    FileOutputStream fileOutputStream
      = new FileOutputStream("yourfile.txt");
    ObjectOutputStream objectOutputStream 
      = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(person);
    objectOutputStream.flush();
    objectOutputStream.close();
    
    FileInputStream fileInputStream
      = new FileInputStream("yourfile.txt");
    ObjectInputStream objectInputStream
      = new ObjectInputStream(fileInputStream);
    Person p2 = (Person) objectInputStream.readObject();
    objectInputStream.close(); 
 
    assertTrue(p2.getAge() == p.getAge());
    assertTrue(p2.getName().equals(p.getName()));
}

We used ObjectOutputStream for saving the state of this object to a file using FileOutputStream. The file “yourfile.txt” is created in the project directory. This file is then loaded using FileInputStream. ObjectInputStream picks this stream up and converts it into a new object called p2.

Finally, we test the state of the loaded object, and it matches the state of the original object.

Notice that the loaded object has to be explicitly cast to a Person type.

3. Java Serialization Caveats

There are some caveats which concern the serialization in Java.

3.1. Inheritance and Composition

When a class implements the java.io.Serializable interface, all its sub-classes are serializable as well. On the contrary, when an object has a reference to another object, these objects must implement the Serializable interface separately, or else a NotSerializableException will be thrown:

public class Person implements Serializable {
    private int age;
    private String name;
    private Address country; // must be serializable too
}

If one of the fields in a serializable object consists of an array of objects, then all these objects must be serializable as well, or else a NotSerializableException will be thrown.

3.2. Serial Version UID

The JVM associates a version (long) number with each serializable class. It is used to verify that the saved and loaded objects have the same attributes and thus are compatible on serialization.

This number can be generated automatically by most IDEs and is based on the class name, its attributes and associated access modifiers. Any changes result in a different number and can cause an InvalidClassException.

If a serializable class doesn’t declare a serialVersionUID, the JVM will generate one automatically at run-time. However, it is highly recommended that each class declares its serialVersionUID as the generated one is compiler dependent and thus may result in unexpected InvalidClassExceptions.

3.3. Custom Serialization in Java

Java specifies a default way in which objects can be serialized. Java classes can override this default behavior. Custom serialization can be particularly useful when trying to serialize an object that has some unserializable attributes. This can be done by providing two methods inside the class that we want to serialize:

private void writeObject(ObjectOutputStream out) throws IOException;

and

private void readObject(ObjectInputStream in) 
  throws IOException, ClassNotFoundException;

With these methods, we can serialize those unserializable attributes into other forms that can be serialized:

public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    private transient Address address;
    private Person person;

    // setters and getters

    private void writeObject(ObjectOutputStream oos) 
      throws IOException {
        oos.defaultWriteObject();
        oos.writeObject(address.getHouseNumber());
    }

    private void readObject(ObjectInputStream ois) 
      throws ClassNotFoundException, IOException {
        ois.defaultReadObject();
        Integer houseNumber = (Integer) ois.readObject();
        Address a = new Address();
        a.setHouseNumber(houseNumber);
        this.setAddress(a);
    }
}
public class Address {
    private int houseNumber;

    // setters and getters
}

The following unit test tests this custom serialization:

@Test
public void whenCustomSerializingAndDeserializing_ThenObjectIsTheSame() 
  throws IOException, ClassNotFoundException {
    Person p = new Person();
    p.setAge(20);
    p.setName("Joe");

    Address a = new Address();
    a.setHouseNumber(1);

    Employee e = new Employee();
    e.setPerson(p);
    e.setAddress(a);

    FileOutputStream fileOutputStream
      = new FileOutputStream("yourfile2.txt");
    ObjectOutputStream objectOutputStream 
      = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(e);
    objectOutputStream.flush();
    objectOutputStream.close();

    FileInputStream fileInputStream 
      = new FileInputStream("yourfile2.txt");
    ObjectInputStream objectInputStream 
      = new ObjectInputStream(fileInputStream);
    Employee e2 = (Employee) objectInputStream.readObject();
    objectInputStream.close();

    assertTrue(
      e2.getPerson().getAge() == e.getPerson().getAge());
    assertTrue(
      e2.getAddress().getHouseNumber() == e.getAddress().getHouseNumber());
}

In this code, we see how to save some unserializable attributes by serializing Address with custom serialization. Note that we must mark the unserializable attributes as transient to avoid the NotSerializableException.

4. Conclusion

In this quick tutorial, we’ve reviewed Java serialization, discussed important things to keep in mind and have shown how to do custom serialization.

As always, the source code used in this tutorial is available over on GitHub.

The new Certification Class of "REST With Spring" is finally out:

>> CHECK OUT THE COURSE