Basic and Custom Serialization


Serialization

Java provides a mechanism, called object serialization where an object can be represented as a sequence of bytes that includes the object's data as well as information about the object's type and the types of data stored in the object.

After a serialized object has been written into a file, it can be read from the file and deserialized that is, the type information and bytes that represent the object and its data can be used to recreate the object in memory.


Basic Serialization in Java

What is Serialization

Serialization is the process of converting an object's state (including its references) to a sequence of bytes, as well as the process of rebuilding those bytes into a live object at some future time. Serialization is used when you want to persist the object. It is also used by Java RMI to pass objects between JVMs, either as arguments in a method invocation from a client to a server or as return values from a method invocation, or as exceptions thrown by remote methods. In general, serialization is used when we want the object to exist beyond the lifetime of the JVM.

java.io.Serializable is a marker interface (has no body). It is just used to "mark" Java classes as serializable.

The serialization runtime associates with each serializable class a version number, called a serialVersionUID, which is used during de-serialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization. If the receiver has loaded a class for the object that has a different serialVersionUID than that of the corresponding sender's class, then deserialization will result in an InvalidClassException. A serializable class can declare its own serialVersionUID explicitly by declaring a field named serialVersionUID that must be static, final, and of type long:

ANY-ACCESS-MODIFIER static final long serialVersionUID = 1L;

How to make a class eligible for serialization

To persist an object the respective class must implement the java.io.Serializable interface.

import java.io.Serializable;
public class SerialClass implements Serializable {
	private static final long serialVersionUID = 1L; 
	private Date currentTime;
 
	public SerialClass() {
		currentTime = Calendar.getInstance().getTime();
	}
 
	public Date getCurrentTime() {
		return currentTime;
	}
}

How to write an object into a file

Now we need to write this object to a file system. We use java.io.ObjectOutputStream for this purpose.

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
public class PersistSerialClass {
	public static void main(String [] args) {
		String filename = "time.ser"; 
		SerialClass time = new SerialClass(); //We will write this object to file system.
		try {
			ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(filename));
			out.writeObject(time); //Write byte stream to file system.
			out.close();
		} catch(IOException ex){
			ex.printStackTrace();
		}
	}
}

How to recreate an object from its serialized state

The stored object can be read from file system at later time using java.io.ObjectInputStream as shown below:

import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.IOException;
import java.io.java.lang.ClassNotFoundException;
 
public class ReadSerialClass {
	public static void main(String [] args) {
		String filename = "time.ser"; 
		SerialClass time = null;
		try {
			ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename));
			time = (SerialClass)in.readObject();
			in.close();
		} catch(IOException ex){
			ex.printStackTrace();
		} catch(ClassNotFoundException cnfe){
			cnfe.printStackTrace();
		}
		// print out restored time
		System.out.println("Restored time: " + time.getTime());
	}
}

The serialized class is in binary form. The deserialization can be problematic if the class definition changes: see the Versioning of Serialized Objects chapter of the Java Serialization Specification for details.

Serializing an object serializes the entire object graph of which it is the root, and operates correctly in the presence of cyclic graphs. A reset() method is provided to force the ObjectOutputStream to forget about objects that have already been serialized.


Custom Serialization in Java

In this example we want to create a class that will generate and output to console, a random number between a range of two integers which are passed as arguments during the initialization

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Random;
 
public class MyRandomGenerator implements Runnable, Serializable {
    private int minValue;
    private int maxValue;
    private Thread myThread;
 
    public MyRandomGenerator(int minValue, int maxValue) {
        this.minValue = minValue;
        this.maxValue = maxValue;
        myThread = new Thread(this);
        myThread.start();
    }
 
    @Override
    private void writeObject(ObjectOutputStream out) throws IOException {
        // Your serialization logic here
    }
 
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        // Your deserialization logic here
    }
 
    public void run() {
        while (true) {
            Random random = new Random();
            System.out.println("Thread ID: " + myThread.getId() + " Random Value: " + random.nextInt(maxValue - minValue));
 
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Now if we want to make this class Serializable there will be some problems. The Thread is one of the certain system-level classes that are not Serializable. So we need to declare the thread as transient. By doing this we will be able to serialize the objects of this class but we will still have an issue. As you can see in the constructor we set the min and the max values of our randomizer and after this we start the thread which is responsible for generating and printing the random value. Thus when restoring the persisted object by calling the readObject() the constructor will not run again as there is no creation of a new object. In that case we need to develop a Custom Serialization by providing two methods inside the class. Those methods are:

private void writeObject(ObjectOutputStream out) throws IOException {
// Your serialization logic here
}
 
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// Your deserialization logic here
}

Thus by adding our implementation in the readObject() we can initiate and start our thread:

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Random;
 
public class MyCustomRandom implements Serializable, Runnable {
    private int minimumValue;
    private int maximumValue;
    private transient Thread myThread;
 
    public MyCustomRandom(int minValue, int maxValue) {
        this.minimumValue = minValue;
        this.maximumValue = maxValue;
        myThread = new Thread(this);
        myThread.start();
    }
 
    @Override
    public void run() {
        while (true) {
            Random random = new Random();
            System.out.println("Thread ID: " + myThread.getId() + " Random Value: " + random.nextInt(maximumValue - minimumValue));
 
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
 
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
    }
 
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        myThread = new Thread(this);
        myThread.start();
    }
}

Here is the main for our example:

import java.io.*;
 
public class MyCustomApp {
    public static void main(String[] args) {
        System.out.println("Greetings!");
        MyCustomRandom myRandom = new MyCustomRandom(5, 15);
 
        FileOutputStream fileOutputStream = null;
        ObjectOutputStream objectOutputStream = null;
 
        try {
            fileOutputStream = new FileOutputStream("mySerializedObject");
            objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(myRandom);
            objectOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        MyCustomRandom deserializedRandom = null;
        FileInputStream fileInputStream = null;
        ObjectInputStream objectInputStream = null;
 
        try {
            fileInputStream = new FileInputStream("mySerializedObject");
            objectInputStream = new ObjectInputStream(fileInputStream);
            deserializedRandom = (MyCustomRandom) objectInputStream.readObject();
            objectInputStream.close();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Basic Programs