Introduction to Java Serialization

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() == person.getAge());
    assertTrue(p2.getName().equals(person.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.

Related posts:

Java Program to Check whether Directed Graph is Connected using BFS
Java Byte Array to InputStream
Java 14 Record Keyword
Java Program to Repeatedly Search the Same Text (such as Bible by building a Data Structure)
How to Remove the Last Character of a String?
Java Program to Represent Graph Using Adjacency List
Guide to the Synchronized Keyword in Java
Java Program to Find the Edge Connectivity of a Graph
Model, ModelMap, and ModelAndView in Spring MVC
The StackOverflowError in Java
HttpClient 4 – Follow Redirects for POST
How to Delay Code Execution in Java
Send an email with an attachment
Java Program to Perform Complex Number Multiplication
Practical Java Examples of the Big O Notation
Java Program to Implement Stack using Two Queues
Xử lý ngoại lệ trong Java (Exception Handling)
Java Program to Find the Minimum value of Binary Search Tree
Remove HTML tags from a file to extract only the TEXT
ThreadPoolTaskExecutor corePoolSize vs. maxPoolSize
Lớp LinkedHashMap trong Java
Giới thiệu Swagger – Công cụ document cho RESTfull APIs
Java Program to Decode a Message Encoded Using Playfair Cipher
Finding Max/Min of a List or Collection
Java Program to Implement First Fit Decreasing for 1-D Objects and M Bins
Jackson – Bidirectional Relationships
Enum trong java
Hướng dẫn sử dụng luồng vào ra ký tự trong Java
Java InputStream to String
Java Program to Check if a Given Graph Contain Hamiltonian Cycle or Not
Java Program to Implement Binomial Tree
Tính kế thừa (Inheritance) trong java