Thursday, August 18, 2022

Serialization and Deserialization in Java

Object serialization is the mechanism of converting object into byte stream. Where as the reverse process of recreating the object from those serialized bytes is known as deserialization. In this post we'll see the process of serialization and deserialization in Java.


Requirement for serialization in Java

A mandatory requirement for a class object to be able to be serialized in Java is that the class should implement Serializable interface which is part of java.io package. Here note that Serializable is a marker interface and does not have any field or method.

Steps for object Serialization in Java

In order to serialize an object in Java you need to do the following-

  • Create an object of ObjectOutputStream.
  • Create an object of OutputStream to be wrapped inside ObjectOutputStream object.
  • Then you need to call writeObject() method which will serialize your object and send it to OutputStream.

Steps for object Deserialization in Java

In order to deserialize an object in Java you need to do the following-

  • Create an object of ObjectInputStream.
  • Create an object of InputStream to be wrapped inside ObjectInputStream object.
  • Then you need to call readObject() method which will reconstitute your object from the byte stream.

Use of serialization in Java

Once an object is converted to a byte stream those bytes can be-

  1. Transmitted from one JVM to another where the object can be reconstituted using those bytes and used in further processing.
  2. Stored on a disk/DB for further use.
Serialization in Java

Java Serialization and Deserialization example

Let’s see an example of serialization and deserialization in Java, here we have an Address bean with fields like addressline1, city etc. and a Person bean that along with other fields also has an instance of address class. That way you can see that the whole object graph is serialized.

Then we have Util class with the methods to serialize and deserialize an object. Also the test class SerializationDemo to execute the code.

Address class

public class Address implements Serializable{
 private String addressLine1;
 private String addressLine2;
 private String city;
 private String state;
 private String country;
 
 Address(String addressLine1, String addressLine2, String city, String state, String country){
  this.addressLine1 = addressLine1;
  this.addressLine2 = addressLine2;
  this.city = city;
  this.state = state;
  this.country = country;
 }
 public String getAddressLine1() {
  return addressLine1;
 }

 public String getAddressLine2() {
  return addressLine2;
 }
 
 public String getCity() {
  return city;
 }
 
 public String getState() {
  return state;
 }
 
 public String getCountry() {
  return country;
 }
}

Person class

public class Person implements Serializable{

 private static final long serialVersionUID = 1L;
 private String name;
 private Date dob;
 // transient
 private transient int age;
 private Address address;
 // Constructor
 Person(String name, Date dob, int age, Address address){
  System.out.println("In Constructor with args");
  this.name = name;
  this.dob = dob;
  this.age = age;
  this.address = address;    
 }
 // no-arg Constructor
 Person(){
  System.out.println("no-arg constructor");
 }
 
 public Address getAddress() {
  return address;
 }

 public String getName() {
  return name;
 }
 
 public Date getDob() {
  return dob;
 }
 
 public int getAge() {
  return age;
 }
}

Util class

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Util {
 
 /**
  * Method used for serialization
  */
 public static void serialzeObject(Object obj, String fileName){
  try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
      new File(fileName)))){
   oos.writeObject(obj);
   
  } catch (FileNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }
 
 /**
  * Method used for deserializing
  */
 public static Object deSerialzeObject(String fileName) throws ClassNotFoundException{
  Object obj = null;
  try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(fileName)))){
   obj = ois.readObject();
   
  } catch (FileNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  return obj;
 }

}

SerializationDemo class

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;


public class SerializationDemo {
 public static void main(String[] args) {
  DateFormat df = new SimpleDateFormat("dd/MM/yyyy");
  Date dob = null;
  try {
   dob = df.parse("04/04/2015");
  } catch (ParseException e1) {
   // TODO Auto-generated catch block
   e1.printStackTrace();
  }
  // Creating and initializaing a Address object
  Address address = new Address("#34", "ABC Street", "New Delhi", "Delhi", "India");
  // Creating and initializaing a Person object
  Person person = new Person("User1", dob, 2, address);
  // file name
  final String fileName = "D://person.ser";
  // serializing
  Util.serialzeObject(person, fileName);
  
  try {
   // deserializing
   person = (Person)Util.deSerialzeObject(fileName);
  } catch (ClassNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  System.out.println("deserialized object ---- ");
  System.out.println("Name " + person.getName());
  System.out.println("DOB " + person.getDob());
  System.out.println("Age " + person.getAge());
  System.out.println("Address Line1 " + person.getAddress().getAddressLine1());
  System.out.println("Address Line2 " + person.getAddress().getAddressLine2());
  System.out.println("City " + person.getAddress().getCity());
  System.out.println("State " + person.getAddress().getState());
  System.out.println("Country " + person.getAddress().getCountry());
  
 }
}

Output

In Constructor with args
deserialized object ---- 
Name User1
DOB Sat Apr 04 00:00:00 IST 2015
Age 0
Age #34
Age ABC Street
Age New Delhi
Age Delhi
Age India

In SerializationDemo class you specify the file name and then pass the person object and the file name to the serializeObject method of the Util class which will do the process of serialization. Again you pass the filename which already has the stored bytes for the person object to the deSerializeObject method of the Util class which will deserialize the serialized person object.

Points to note here

  • Here you can see in person object there is a reference to address object and both are serialized which shows the whole object graph gets serialized even if there is a very complex hierarchy.
  • All the classes which are to be serialized should be implementing Serializable interface. In this example suppose Address class doesn’t implement Serializable interface and there is a reference of it in Person class then you will get exception when you try to serialize the person object.
    java.io.NotSerializableException: org.test.Address
  • If you have noticed in Person class there is a system.println in both with args and no-arg constructors. When the object is deserialized you can see even default no-arg constructor is not called. Though reiterated several times I’ll repeat it again that object is reconstituted from the serialized bytes while deserializing and no constructor is called during the process of deserialization.
  • In Person class age field is marked as transient which stops this field from getting serialized. That’s why you can see age is printed as 0. Refer Transient in Java to know more about transient keyword.

Implementing writeObject() and readObject() methods

Default mechanism for serialization in Java for us, as end user, is automatic. If you want to have some control over the process of serialization and deserialization in Java then you can implement writeObject() and readObject() methods in your own class.

If you add methods writeObject() and readObject(), these methods will be called automatically when the object is serialized and deserialized respectively and provide the serialization mechanism rather than using the default mechanism. General form of writeObject() and readObject() methods is as follows.

private void writeObject(ObjectOutputStream oos) throws IOException

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException

writeObject() and readObject() methods - Serialization with inheritance

One scenario where writeObject() and readObject() methods can be used quite effectively is in a parent-child relationship where parent class doesn’t implement the serializable interface but you want the fields that are there in the parent class to be serialized too. Let’s say we have a classA which is the super class and classB which extends classA.

ClassA

public class ClassA {
 private int deptId;
 private String deptName;
 public int getDeptId() {
  return deptId;
 }
 public void setDeptId(int deptId) {
  this.deptId = deptId;
 }
 public String getDeptName() {
  return deptName;
 }
 public void setDeptName(String deptName) {
  this.deptName = deptName;
 }
 
}

ClassB

import java.io.Serializable;

public class ClassB extends ClassA implements Serializable{
 /**
  * 
  */
 private static final long serialVersionUID = 7280011452607087533L;
 private String empId;
 private String empName;
 public String getEmpId() {
  return empId;
 }
 public void setEmpId(String empId) {
  this.empId = empId;
 }
 public String getEmpName() {
  return empName;
 }
 public void setEmpName(String empName) {
  this.empName = empName;
 }
 
}

In this case if you create a ClassB object and serialize it. Then deserializing it won’t give you any value for the fields which ClassB object gets by virtue of extending ClassA.

public class TestSerialize{
 
 public static void main(String[] args) {
  final String fileName = "D://test.ser";
  ClassB objb = new ClassB();
  objb.setDeptId(1);
  objb.setDeptName("Finance");
  objb.setEmpId("E001");
  objb.setEmpName("Ram");
  Util.serialzeObject(objb, fileName);
  try {
   objb = (ClassB)Util.deSerialzeObject(fileName);
  } catch (ClassNotFoundException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  System.out.println("DeptId " + objb.getDeptId() + " DeptName " + objb.getDeptName());
  System.out.println("EmpId " + objb.getEmpId() + " EmpName " + objb.getEmpName());
 }

}

Output

DeptId 0 DeptName null
EmpId E001 EmpName Ram

You can see that the deptId and deptName fields which are in ClassA are not serialized as ClassA doesn’t implement Serializable interface.

In this case you will have to add writeObject and readObject methods and provide functionality so that the fields of ClassA are also serialized. So, your updated ClassB with writeObject() and readObject() methods added will look like –

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

public class ClassB extends ClassA implements Serializable{
 /**
  * 
  */
 private static final long serialVersionUID = 7280011452607087533L;
 private String empId;
 private String empName;
 public String getEmpId() {
  return empId;
 }
 public void setEmpId(String empId) {
  this.empId = empId;
 }
 public String getEmpName() {
  return empName;
 }
 public void setEmpName(String empName) {
  this.empName = empName;
 }
 
 // adding writeObject method
 private void writeObject(ObjectOutputStream oos) throws IOException{
  // calling default functionality for classB fields
  oos.defaultWriteObject();
  // Explicitly writing ClassA fields
  oos.writeInt(getDeptId());
  oos.writeObject(getDeptName());
 }
 
 // adding readObject method
 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException{
  // calling default functionality for classB fields
  ois.defaultReadObject();
  // Explicitly reading ClassA fields and setting them
  setDeptId(ois.readInt());
  setDeptName((String)ois.readObject());
 }
}

Output

DeptId 1 DeptName Finance
EmpId E001 EmpName Ram

Running it using the TestSerialize class will now give you proper output.

Portability with Java serialization

One important feature of serialization in Java is the portability it offers. When a serialized object is transmitted across the network the serialization mechanism will take into account the differences in operating systems. Thus an object converted into byte streams in Windows OS can be transmitted across the network and deserialized into an object in the Unix operating system.

Points to note here

  1. One thing to be careful about here is that the order of reading the fields should be same as the order they were written.
  2. You can use defaultWriteObject() and defaultReadObject() as the first statement in writeObject() and readObject() methods respectively for using the default serialization mechanism.

That's all for this topic Serialization and Deserialization in Java. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Java Advanced Tutorial Page


Related Topics

  1. Externalizable Interface in Java
  2. Transient Keyword in Java With Examples
  3. SerialVersionUID And Versioning in Java Serialization
  4. Marker Interface in Java
  5. Java Object Cloning - clone() Method

You may also like-

  1. How ArrayList Works Internally in Java
  2. Difference Between ArrayList And CopyOnWriteArrayList in Java
  3. Fail-Fast Vs Fail-Safe Iterator in Java
  4. Java ReentrantReadWriteLock With Examples
  5. Spliterator in Java
  6. Exception Handling in Java Lambda Expressions
  7. BigDecimal in Java With Examples
  8. Is String Thread Safe in Java