Signup/Sign In

Java Serialization and Deserialization

Serialization means converting an object to a byte stream. Deserialization, as the name suggests, is the reverse process of serialization. Deserialization generates an object from a byte stream.

Serialization and Deserialization are performed to save or persist the state of objects. It is also used to transmit objects over a network. The byte stream generated through Serialization is platform-independent.

So, an object can be serialized on one system and transmitted to another, where it can be deserialized to get the original object.

In this tutorial, we will learn how to serialize and deserialize objects in Java.

Serialization and Deserialization

Serialization and Deserialization

  • A class should implement the Serializable interface to make its objects serializable.
  • The Serializable interface is a marker interface which means that it does not have any fields or methods.
  • If a class implements the Serializable interface, then all its subclasses will also be serializable. The subclasses don't need to implement the Serializable interface.
  • Only non-static fields are serialized, and static fields will not be serialized.
  • Note that transient fields are completely ignored during serialization and will be deserialized to the default values.
  • If an object contains references to other objects, then those objects should also be serializable. Otherwise, a NotSerializableException is thrown.

ObjectOutputStream

The ObjectOutputStream class extends the OutputStream class. It is used to write objects as bytes to an output stream. The writeObject() method of this class converts a serializable object into a stream of bytes.

public final void writeObject(Object o) throws IOException

ObjectInputStream

The ObjectInputStream class extends the InputStream class and is used to read a stream of bytes and generate an object from it. The readObject() method of this class is used for deserialization.

public final Object readObject() throws IOException, ClassNotFoundException

Serialization and Deserialization Example

Let's try to serialize and deserialize an object of the Student class shown below.

class Student implements Serializable
{
	String name;
	Double gpa;
	static int regNo;
	transient boolean isHosteller;
	
	Student(String name, Double gpa, int regNo, boolean isHosteller)
	{
		this.name = name;
		this.gpa = gpa;
		this.regNo = regNo;
		this.isHosteller = isHosteller;
	}	
	public void print()
	{
		System.out.println("Name: " + this.name);
		System.out.println("GPA: " + this.gpa);
		System.out.println("Registration Number: " + this.regNo);
		System.out.println("Is Hosteller: " + this.isHosteller);
	}
}

The transient field will be ignored during the serialization process. It will be deserialized to the default boolean value(false). The static field will take the currently set value. Notice in the code below when we alter the registration number, then the registration number of the deserialized object also changes. This happens because the regNo field is static.

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Demo
{
	public static void main(String[] args) throws IOException, ClassNotFoundException
	{
		Student student = new Student("Justin", 8.51, 101, true);
		System.out.println("Before Deserialization:");
		student.print();
		
		//Serialization
		FileOutputStream fileOut = new FileOutputStream("demo.txt");
		ObjectOutputStream oos = new ObjectOutputStream(fileOut);
		oos.writeObject(student);
		oos.close();
		fileOut.close();
		
		student.regNo = 102;//Changing the static field
		
		//Deserialization
		FileInputStream fileIn = new FileInputStream("demo.txt");
		ObjectInputStream ois = new ObjectInputStream(fileIn);
		
		Student deserializedStudent = (Student) ois.readObject();
		
		System.out.println("\nAfter Deserialization:");
		deserializedStudent.print();
		ois.close();
		fileIn.close();
	}
}


Before Deserialization:
Name: Justin
GPA: 8.51
Registration Number: 101
Is Hosteller: true

After Deserialization:
Name: Justin
GPA: 8.51
Registration Number: 102
Is Hosteller: false

NotSerializableException Example

If a Student class contains a field of Address class, then the Address class must also be serializable to make the Student class Serializable. If this is not followed, then a NotSerializableException is thrown. Let's try to simulate the NotSerializableException.

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Student implements Serializable
{
	String name;
	Address add;
	
	Student(String s, Address a)
	{
		this.name = s;
		this.add = a;
	}
}
class Address//Does not implements Serializable
{
	int postalCode;
	Address(int i)
	{
		this.postalCode = i;
	}
}
public class Demo
{
	public static void main(String[] args) throws IOException, ClassNotFoundException
	{
		Address add = new Address(10001);
		Student student = new Student("Justin", add);
		
		//Serialization
		FileOutputStream fileOut = new FileOutputStream("demo.txt");
		ObjectOutputStream oos = new ObjectOutputStream(fileOut);
		oos.writeObject(student);
		oos.close();
		fileOut.close();
	}
}


Exception in thread "main" java.io.NotSerializableException: Address
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1193)
at java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1579)
at java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1536)
at java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1444)
at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1187)
at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:353)
at Demo.main(Demo.java:38)

Serial Version UID

Every class that implements the Serializable interface has a version number of type long associated with it. This ID is used to verify that the correct classes are loaded and have the same attributes during serialization and deserialization. If there is any discrepancy, then an InvalidClassException is thrown.

We can either declare this ID ourselves or make the JVM do it for us. However, it is recommended to provide a version number by ourselves because the one generated by the JVM is compiler-dependent. This can lead to InvalidClassExceptions. This field must be static and final, and we can use any access modifier.

public static final long serialversionUID = 123L;

Custom Serialization and Deserialization

We can also customize the default serialization and deserialization process. This is done by implementing the writeObject() and readObject() in our class.

private void writeObject(ObjectOutputStream oos) throws IOException
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException

Custom serialization and deserialization are usually used when for ignored fields(like transient). Instead of completely ignoring these fields, we can define custom behavior for them.

For example, in the code below, the deserialized object contains null as the registration number.

class Student implements Serializable
{
	String name;
	Double gpa;
	transient String regNo;
	
	Student(String name, Double gpa, String regNo)
	{
		this.name = name;
		this.gpa = gpa;
		this.regNo = regNo;
	}	
	public void print()
	{
		System.out.println("Name: " + this.name);
		System.out.println("GPA: " + this.gpa);
		System.out.println("Registration Number: " + this.regNo);
	}
}
public class Demo
{
	public static void main(String[] args) throws IOException, ClassNotFoundException
	{
		Student student = new Student("Justin", 8.51, "101");
		System.out.println("Before Deserialization:");
		student.print();
		
		//Serialization
		FileOutputStream fileOut = new FileOutputStream("demo.txt");
		ObjectOutputStream oos = new ObjectOutputStream(fileOut);
		oos.writeObject(student);
		oos.close();
		fileOut.close();
		
		//Deserialization
		FileInputStream fileIn = new FileInputStream("demo.txt");
		ObjectInputStream ois = new ObjectInputStream(fileIn);
		
		Student deserializedStudent = (Student) ois.readObject();
		
		System.out.println("\nAfter Deserialization:");
		deserializedStudent.print();
		ois.close();
		fileIn.close();
	}
}


Before Deserialization:
Name: Justin
GPA: 8.51
Registration Number: 101

After Deserialization:
Name: Justin
GPA: 8.51
Registration Number: null

Let's say we want to serialize the transient regNo field. When we serialize this field, then the current year should be added to the registration number. This will be reflected in the deserialized object. We also need to use defaultReadObject() and defaultWriteObject() to serialize the other fields in the default manner. The updated class with the writeObject() and readObject() methods is shown below.

class Student implements Serializable
{
	String name;
	Double gpa;
	transient String regNo;
	
	Student(String name, Double gpa, String regNo)
	{
		this.name = name;
		this.gpa = gpa;
		this.regNo = regNo;
	}	
	private void writeObject(ObjectOutputStream oos)throws IOException
	{
		oos.defaultWriteObject();//For default serialization of Student class		
		//Adding current year to the registration number
		LocalDate d = LocalDate.now();
		this.regNo = d.getYear() + "-" + this.regNo;
		
		//serialization of regNo
		oos.writeObject(this.regNo);
	}	
	private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException
	{
		ois.defaultReadObject();////For default deserialization of Student class
		String s = (String)ois.readObject();
		this.regNo = s;
	}	
	public void print()
	{
		System.out.println("Name: " + this.name);
		System.out.println("GPA: " + this.gpa);
		System.out.println("Registration Number: " + this.regNo);
	}
}

Let's test our custom serialization and deserialization process.

public class Demo
{
	public static void main(String[] args) throws IOException, ClassNotFoundException
	{
		Student student = new Student("Justin", 8.51, "101");
		System.out.println("Before Deserialization:");
		student.print();
		
		//Serialization
		FileOutputStream fileOut = new FileOutputStream("demo.txt");
		ObjectOutputStream oos = new ObjectOutputStream(fileOut);
		oos.writeObject(student);
		oos.close();
		fileOut.close();		
		
		//Deserialization
		FileInputStream fileIn = new FileInputStream("demo.txt");
		ObjectInputStream ois = new ObjectInputStream(fileIn);		
		Student deserializedStudent = (Student) ois.readObject();		
		System.out.println("\nAfter Deserialization:");
		deserializedStudent.print();
		ois.close();
		fileIn.close();
	}
}


Before Deserialization:
Name: Justin
GPA: 8.51
Registration Number: 101

After Deserialization:
Name: Justin
GPA: 8.51
Registration Number: 2021-101

Summary

Serialization and deserialization are often performed when storing or transferring objects. They allow us to convert an object into a platform-independent byte of streams and deserialize the stream on some other system. Serialization is performed using the writeObject() method of the ObjectOutputStream. Deserialization is performed using the readObject() method of the ObjectInputStream. We can also define custom behavior for serialization and deserialization.



About the author:
I am a 3rd-year Computer Science Engineering student at Vellore Institute of Technology. I like to play around with new technologies and love to code.