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:

Feign – Tạo ứng dụng Java RESTful Client
Spring WebClient Filters
Java Program to Implement Sorted Vector
Converting Java Date to OffsetDateTime
Java Program to implement Dynamic Array
A Guide to JPA with Spring
Intro to Spring Boot Starters
Converting Iterator to List
Spring 5 and Servlet 4 – The PushBuilder
Tránh lỗi ConcurrentModificationException trong Java như thế nào?
Quick Guide to the Java StringTokenizer
Refactoring Design Pattern với tính năng mới trong Java 8
Java – Delete a File
An Intro to Spring Cloud Task
Introduction to Spliterator in Java
OAuth2 for a Spring REST API – Handle the Refresh Token in Angular
Getting Started with Custom Deserialization in Jackson
A Quick JUnit vs TestNG Comparison
Sắp xếp trong Java 8
Giới thiệu JDBC Connection Pool
Java Program to Apply Above-Below-on Test to Find the Position of a Point with respect to a Line
Jackson – Marshall String to JsonNode
Truyền giá trị và tham chiếu trong java
Stack Memory and Heap Space in Java
Java Program to Implement the Alexander Bogomolny’s UnOrdered Permutation Algorithm for Elements Fro...
Thực thi nhiều tác vụ cùng lúc như thế nào trong Java?
Java Program to Check Whether an Undirected Graph Contains a Eulerian Cycle
Java Program to Check Whether it is Weakly Connected or Strongly Connected for a Directed Graph
Java Program to Implement Circular Doubly Linked List
Consumer trong Java 8
Logout in an OAuth Secured Application
Java Program to Implement the Hungarian Algorithm for Bipartite Matching