Serialization and De-Serialization in Java


When you create a class, you may create an object for that particular class and once we execute/terminate the program, the object is destroyed by itself via the garbage collector thread.

In Java, we create several objects that live and die accordingly, and every object will certainly die when the JVM dies. But sometimes, we might want to reuse an object between several JVMs or we might want to transfer an object to another machine over the network.

what happens if you want to call that class without re-creating the object? in those cases, what you do is use the serialization concept by converting data into a byte stream.

Well, serialization allows us to convert the state of an object into a byte stream, which then can be saved into a file on the local disk or sent over the network to any other machine. And deserialization allows us to reverse the process, which means reconverting the serialized byte stream to an object again.

Object serialization is a vital process in Java, allowing the conversion of an object's state into a byte stream. This byte stream can be persisted into a disk/file or transmitted over the network to another running Java Virtual Machine (JVM). The beauty of this process lies in its platform independence – the byte stream created on one platform can seamlessly be deserialized on a different platform.

In simpler terms, object serialization involves saving an object's state as a sequence of bytes, while deserialization reconstructs the object from those bytes. While the overall process is often referred to as serialization, it's beneficial to distinguish between serialization and deserialization for clarity.

This dual process ensures that the state of an object can be efficiently saved, transported, and reconstructed across various platforms, providing flexibility and compatibility in Java applications.

Serialization & De- Serialization












A class must implement Serializable interface present in java.io package in order to serialize its object successfully. Serializable is a marker interface that adds serializable behavior to the class implementing it.

Java provides Serializable API encapsulated under java.io package for serializing and deserializing objects which include,
  • java.io.serializable
  • java.io.Externalizable
  • ObjectInputStream
  • ObjectOutputStream
  • etc..
Java Marker interface

Marker Interface is a special interface in Java without any field and method. Marker interface is used to inform compiler that the class implementing it has some special behavior or meaning. Some example of Marker interface are,
  • java.io.serializable
  • java.lang.Cloneable
  • java.rmi.Remote
  • java.util.RandomAccess
All these interfaces does not have any method and field. They only add special behavior to the classes implementing them. However marker interfaces have been deprecated since Java 5, they were replaced by Annotations. Annotations are used in place of Marker Interface that play the exact same role as marker interfaces did before.

To implement serialization and deserialization, Java provides two classes ObjectOutputStream and ObjectInputStream.

ObjectOutputStream class

:It is used to write object states to the file. An object that implements java.io.Serializable interface can be written to strams. It provides various methods to perform serialization.
  • Constructor
    • public ObjectOutputStream(OutputStream out) throws IOException {}  : It creates an ObjectOutputStream that writes to the specified OutputStream.
  • Important Method
    • public final void writeObject(Object obj) throws IOException {} : It writes the specified object to the ObjectOutputStream.
    •  public void flush() throws IOException {} : It flushes the current output stream.
    • public void close() throws IOException {}: It closes the current output stream.

ObjectInputStream class

An ObjectInputStream deserializes objects and primitive data written using an ObjectOutputStream.
  • Constructor
    • public ObjectInputStream(InputStream in) throws IOException {} :It creates an ObjectInputStream that reads from the specified InputStream.
  • Important Methods
    • public final Object readObject() throws IOException, ClassNotFoundException{} :It reads an object from the input stream.
    • public void close() throws IOException {} : It closes ObjectInputStream.

NOTE: Static members are never serialized because they are connected to class not object of class.

transient Keyword

While serializing an object, if we don't want certain data member of the object to be serialized we can mention it transient. transient keyword will prevent that data member from being serialized. simply the transient modifier/keyword is applicable only for variables but not for methods and classes.

at the time of serialization, if we don't want to serialize the value of a particular variable to meet security constraints, then we should declare that variable as transient.

while performing serialization, the jvm ignores the original value of the transient variable and save default value to the file. hence, transient means not to serialize.

  • transient vs. static
    • a static variable is not part of an object state, and hence, it won't participate in serialization. due to this declaring static variable as transient, there is no use.
  • final vs. transient
    • final variables will be participated in serialization directly by the value. hence, declaring a final variable as transient causes no impact.
class studentinfo implements Serializable
{
    String name;
    transient int rid;
    static String contact;
}
  • Making a data member transient will prevent its serialization.
  • In this example rid will not be serialized because it is transient, and contact will also remain unserialized because it is static.

Serializing an Object

The ObjectOutputStream class is used to serialize an Object. The following SerializeDemo program instantiates an Employee object and serializes it to a file.

When the program is done executing, a file named employee.ser is created. The program does not generate any output, but study the code and try to determine what the program is doing.

Note − When serializing an object to a file, the standard convention in Java is to give the file a .ser extension.

Example: Serializing an Object in Java

In this example, we have a class that implements Serializable interface to make its object serialized.

import java.io.*;
class Studentinfo implements Serializable
{
    String name;
    int rid;
    static String contact;
    Studentinfo(String n, int r, String c)
    {
    this.name = n;
    this.rid = r;
    this.contact = c;
    }
}

class Demo
{
    public static void main(String[] args)
    {
        try
        {
            Studentinfo si = new Studentinfo("Deepak Singh", 292, "952535");
            FileOutputStream fos = new FileOutputStream("student.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(si);
            oos.flush();
            oos.close();
        }
        catch (Exception e)
        {
            System.out.println(e);
        }
    }
}


Object of Studentinfo class is serialized using writeObject() method and written to student.txt file.

Example 2 :

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializeDemo {

   public static void main(String [] args) {
      Employee e = new Employee();
      e.name = "Deepak Singh";
      e.address = "Bhagalpur Bihar, India";
      e.SSN = 813105;
      e.number = 292;
     
      try {
         FileOutputStream fileOut = new FileOutputStream("employee.ser");
         ObjectOutputStream out = new ObjectOutputStream(fileOut);
         out.writeObject(e);
         out.close();
         fileOut.close();
         System.out.printf("Serialized data is saved in /tmp/employee.ser");
      } catch (IOException i) {
         i.printStackTrace();
      }
   }
}
class Employee implements java.io.Serializable {
    private static final long serialVersionUID = 1L;
    public String name;
    public String address;
    public transient int SSN;
    public int number;
   
    public void mailCheck() {
       System.out.println("Mailing a check to " + name + " " + address);
    }
 }

Output:
Serialized data is saved in /tmp/employee.ser

Deserializing an Object

Deserialization is the process of reconstructing the object from the serialized state. It is the reverse operation of serialization. Let's see an example where we are reading the data from a deserialized object.

The following DeserializeDemo program deserializes the Employee object created in the earlier program. Study the program and try to determine its output 

Example for Deserializing an Object

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class DeserializeDemo {

   public static void main(String [] args) {
      Employee e = null;
      try {
         FileInputStream fileIn = new FileInputStream("employee.ser");
         ObjectInputStream in = new ObjectInputStream(fileIn);
         e = (Employee) in.readObject();
         in.close();
         fileIn.close();
      } catch (IOException i) {
         i.printStackTrace();
         return;
      } catch (ClassNotFoundException c) {
         System.out.println("Employee class not found");
         c.printStackTrace();
         return;
      }

      System.out.println("Deserialized Employee...");
      System.out.println("Name: " + e.name);
      System.out.println("Address: " + e.address);
      System.out.println("SSN: " + e.SSN);
      System.out.println("Number: " + e.number);
   }
}
class Employee implements java.io.Serializable {

   private static final long serialVersionUID = 1L;
   public String name;
   public String address;
   public transient int SSN;
   public int number;
   
   public void mailCheck() {
      System.out.println("Mailing a check to " + name + " " + address);
   }
}

Output :

Deserialized Employee...
Name: Deepak Singh
Address:Bhagalpur Bihar, India
SSN: 813105
Number:292

Example 2: 

import java.io.*;

class Studentinfo implements Serializable
{
    String name;
    int rid;
    static String contact;
    Studentinfo(String n, int r, String c)
    {
    this.name = n;
    this.rid = r;
    this.contact = c;
    }
}

class Demo
{
    public static void main(String[] args)
    {
        Studentinfo si=null ;
        try
        {
            FileInputStream fis = new FileInputStream("/filepath/student.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
            si = (Studentinfo)ois.readObject();
        }
        catch (Exception e)
        {
            e.printStackTrace(); }
            System.out.println(si.name);
            System.out. println(si.rid);
            System.out.println(si.contact);
        }
}


Output:
Deepak Singh
292
null

what is the serialversionuid?

serialversionuid is an id, which is stamped on an object when it gets serialized usually with the hashcode of the object. we can find serialversionuid for the object by the serialver tool in java.

syntax: serialver classname

serialversionuid is used for version control of an object. the consequence of not specifying serialversionuid is that when you add or modify any field in the class, then the already-serialized class will not be able to recover because the serialversionuid was generated for the new class and the old serialized object will be different. the java serialization process relies on correct serialversionuid for recovering the state of the serialized object and throws java.io.invalidclassexception in case of serialversionuid mismatch.


Serial Version UID in Java

The SerialVersionUID is a crucial aspect of Java serialization, acting as a version identifier for serializable classes during the serialization and deserialization processes.

During runtime, the serialization process associates a unique identifier (Serial Version UID) with each serializable class. This identifier plays a vital role in verifying the consistency between the sender and receiver of serialized objects. It ensures that the classes on both ends match, promoting compatibility.

For proper verification, it is necessary that both the sender and receiver have the same SerialVersionUID. If there is a mismatch, indicated by a difference in the SerialVersionUID, it results in an InvalidClassException during the deserialization process.

To explicitly declare your own SerialVersionUID in a serializable class, you create a field named serialVersionUID and assign it a value. This field should be of type long, static, final, and preferably private. Explicitly declaring the SerialVersionUID provides control over versioning and is recommended for maintaining compatibility between different versions of a class.

import java.io.Serializable;
public class Employee implements Serializable {
    private static final long serialVersionUID = 123L;
    String name;
    int id;
   
    public Employee( String name, int id ) {
        this .name = name;
        this .id = id;
    }
}
Serialization Proxy Pattern

Serialization in java comes with some serious pitfalls such as;
  • The class structure can’t be changed a lot without breaking the java serialization process. So even though we don’t need some variables later on, we need to keep them just for backward compatibility.
  • Serialization causes huge security risks, an attacker can change the stream sequence and cause harm to the system. For example, user role is serialized and an attacker change the stream value to make it admin and run malicious code.
Java Serialization Proxy pattern is a way to achieve greater security with Serialization. In this pattern, an inner private static class is used as a proxy class for serialization purpose. This class is designed in the way to maintain the state of the main class. This pattern is implemented by properly implementing readResolve() and writeReplace() methods. Let us first write a class which implements serialization proxy pattern and then we will analyze it for better understanding.

package com.journaldev.serialization.proxy;

import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class Data implements Serializable{

    private static final long serialVersionUID = 2087368867376448459L;

    private String data;
   
    public Data(String d){
        this.data=d;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
   
    @Override
    public String toString(){
        return "Data{data="+data+"}";
    }
   
    //serialization proxy class
    private static class DataProxy implements Serializable{
   
        private static final long serialVersionUID = 8333905273185436744L;
       
        private String dataProxy;
        private static final String PREFIX = "ABC";
        private static final String SUFFIX = "DEFG";
       
        public DataProxy(Data d){
            //obscuring data for security
            this.dataProxy = PREFIX + d.data + SUFFIX;
        }
       
        private Object readResolve() throws InvalidObjectException {
            if(dataProxy.startsWith(PREFIX) && dataProxy.endsWith(SUFFIX)){
            return new Data(dataProxy.substring(3, dataProxy.length() -4));
            }else throw new InvalidObjectException("data corrupted");
        }
       
    }
   
    //replacing serialized object to DataProxy object
    private Object writeReplace(){
        return new DataProxy(this);
    }
   
    private void readObject(ObjectInputStream ois) throws InvalidObjectException{
        throw new InvalidObjectException("Proxy is not used, something fishy");
    }
}

  • Both Data and DataProxy class should implement Serializable interface.
  • DataProxy should be able to maintain the state of Data object.
  • DataProxy is inner private static class, so that other classes can’t access it.
  • DataProxy should have a single constructor that takes Data as argument.
  • Data class should provide writeReplace() method returning DataProxy instance. So when Data object is serialized, the returned stream is of DataProxy class. However DataProxy class is not visible outside, so it can’t be used directly.
  • DataProxy class should implement readResolve() method returning Data object. So when Data class is deserialized, internally DataProxy is deserialized and when it’s readResolve() method is called, we get Data object.
  • Finally implement readObject() method in Data class and throw InvalidObjectException to avoid hackers attack trying to fabricate Data object stream and parse it.

Java Deserialize Vulnerabilities

In simple words, Java deserialize vulnerabilities are security vulnerabilities that occur when undesired or modified objects are inserted during the process of serialization-deserialization by malicious activities. Let's consider that for your Java application. You're reconstructing the object from the byte stream. So you're expecting the already serialized object, let's say obj1. However, instead of obj1, you get obj2. The retrieved object is thus a result of some malicious activities; this is a Java deserialize vulnerability. Untrusted and malicious byte-streams can easily exploit vulnerable deserialization code.

How to Prevent a Java Deserialize Vulnerability?

Following approaches can be followed to prevent Java deserialize vulnerability:
  • The most basic approach is performing inspection of the objects from a deserialized object stream or in other words, basic filtration of the ObjectInputStream. There are several libraries to perform these validation actions.
  • The other way is to forbid objects of some classes from being deserialized, the blacklisting approach.
  • We can also allow a set of objects of approved classes to get deserialized, the whitelist approach. Deserialization will occur in a restrictive manner thus avoiding the chances of deserialize vulnerabilities.
  • Keep the open source libraries up to date.

Serialization in Java with Inheritance

In OOP, the capability of a class to derive properties and characters from the other class is called inheritance. We have a Parent/Super class or Child/Derived class. Let's take an example:

import java.io.* ;

class A implements Serializable {
    int a;
    int b;

    A()  {
        this.a = 10;
        this.b = 5;
    }
}

public class B extends A {
    public static void  main(String[] args) throws IOException {

    try  {
        A objA = new A();

               //Creating stream and writing the object
        FileOutputStream fout = new  FileOutputStream("file.txt");

        ObjectOutputStream out = new ObjectOutputStream(fout);

        out.writeObject(objA);
        out.flush();
        out.close();

        System.out.println("Serialization successful!");
        }

        catch(Exception e)  {
            System.out.println(e);
        }
    }
}

Output :
Serialization successfull

So in the example above, Class A is the parent class, and class B inherits from Class A. As you can see, Class B does not implement Serializable but is still eligible for serialization. This is because, in Java child class doesn't have to implement serializable if parent class has already done so.

Serialization in Java With Aggregation

Aggregation in OOP is a method to reuse a class, basically a class defines another class as an entity reference.

import java.io. *  ;

class A implements Serializable {
    int a;
    int b;

    A()  {
        this.a = 10;
        this.b = 5;
    }
}

public class B {
    public static void  main(String[] args) throws IOException {

    try {
        A objA = new  A();
       
               //Creating stream and writing the object
        FileOutputStream fout = new  FileOutputStream("file.txt");

        ObjectOutputStream out = new ObjectOutputStream(fout);

        out.writeObject(objA);
        out.flush();
        out.close();

        System.out.println("Serialization successful");
        }

        catch(Exception e)  {
            System.out.println(e);
        }
    }
}


Output:

java.io.NotSerializableException

The code results in an error because, when a class has a reference to another class, all individual references must implement a Serializable interface too.


Serialization in Java With Array or Collection

import java.io. *  ;

class A implements Serializable {
    int a;
    int b;

    A() {
        this.a = 10;
        this.b = 5;
    }
}

public class B {
    public static void  main(String[] args) throws IOException {

    try  {
        int arr[] = new int[10];
        A objA = new  A();
       
        //Creating stream and writing the object
        FileOutputStream fout = new  FileOutputStream("file.txt");

        ObjectOutputStream out = new ObjectOutputStream(fout);

        out.writeObject(arr);
        out.flush();
        out.close();

        System.out.println("Serialization successful!");
        }

        catch(Exception e)  {
            System.out.println(e);
        }
    }
}

Output:
Serialization successful!

If any of the fields in a serializable object consists of an array of objects, in that case, all the objects must be serializable as well. Otherwise, a NotSerializableException shall occur.

Serialization with the static data member

If there is any static data member in a class, it will not be serialized because static is the part of class not object.

class Employee implements Serializable
{  
    int id;  
    String name;  
    static String company="Deve292 IT Pvt Ltd";//it won't be serialized  
    public Student(int id, String name)
     {  
         this.id = id;  
        this.name = name;  
    }  
   }  


  • Serialization Process:
    • Serialization is the process of saving an object's state to a sequence of bytes.
    • Deserialization is the process of reconstructing an object from those bytes.
    • Serialized objects can be stored on a file or sent over the network.
  • Serializable Interface:
    • Only subclasses of the Serializable interface can be serialized.
  • Exceptions:
    • If a class does not implement the Serializable interface or has a reference to a non-Serializable class, NotSerializableException is thrown.
  • Field Serialization:
    • Transient and static fields do not get serialized.
  • SerialVersionUID:
    • SerialVersionUID is used to verify compatibility during deserialization.
    • Explicitly creating a SerialVersionUID is recommended to handle class structure changes and avoid InvalidClassException.
  • Custom Serialization:
    • Override writeObject and readObject methods to customize serialization behavior.
    • Use ObjectOutputStream.defaultWriteObject() and ObjectInputStream.defaultReadObject() for default logic.
  • NotSerializableException:
    • Throw NotSerializableException from writeObject and readObject if serialization or deserialization is not desired for a class.

  • Points to Remember:
    • If a parent class implements Serializable, the child class doesn't need to implement it, but vice-versa is not true.
    • Only non-static data members are saved via serialization.
    • Static and transient data members are not saved via serialization.
    • Constructors of objects are never called during deserialization.
    • To avoid saving the value of a non-static data member, mark it as transient.
    • During deserialization, if the JVM can't find a class, it throws ClassNotFoundException.
The Java Serialization process can be further customized and enhanced using the Externalizable interface, which I have explained in How to Customize Serialization In Java By Using Externalizable Interface.

Comments