Wednesday, May 24, 2023

Java Serialization Example - How to Serialize and Deserialize Objects in Java?Tutorial

Serialization is one of the important but confusing concepts in Java. Even experienced Java developers struggle to implement Serialization correctly. The Serialisation mechanism is provided by Java to save and restore the state of an object programmatically. Java provides two classes Serializable and Externalizable in java.io package to facilitate this process, both are marker interfaces i.e. an interface without any methods. Serializing an Object in Java means converting it into a wire format so that you can either persist its state in a file locally or transfer it to another client via the network, hence it becomes an extremely important concept in distributed applications running across several JVMs.

There are other features in Java like Remote Method Invocation (RMI) or HttpSession in Servlet API which mandates the participating object should implement a Serializable interface because they may be transferred and saved across the network.

In this article, I have tried to explain the concept and process of Serialization by taking a simple example of a general-purpose object in Java. We have a class called Shoe, which represents a Shoe, which has an instance variable to represent id, color, and size, and a static variable to represent a brand. I have included both transient and static variables to prove that those fields are not saved during the Serialization process.

You can also read Head First Java to learn more about the subtle details of Serialization in Java. They have covered the serialization topic just right, with neither too much detail nor trivial explanation. I have learned a lot of useful details from there.

I have also explained the reverse process of Serialization to restore the state of the object i.e. de-serialization and why SerialVersionUID is important for any serializable class. Once we restore the object we print its state to compare values before serialization. This will give you a clear idea of how you can save and restore objects in Java.

And, If you are new to the Java world then I also recommend you go through The Complete Java MasterClass on Udemy to learn Java in a better and more structured way. This is one of the best and up-to-date courses to learn Java online.



Java Program to Serialize an Object using Serializable interface

Here is our Java program to demonstrate how to serialize and de-serialize an object in Java. This program contains two classes Shoe and SerializationDemo, the first class is our POJO, we will create an object of this class and serialize it. 

The second class is our application class which contains the main() method, all the code to create an object, saving it, and finally restoring it written inside the main method.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.logging.Logger;

/**
 * Simple example of Serialization in Java. We first create a Shoe object, then
 * serializes it and finally restored it by using de-serialization.
 *
 * @author Javin Paul
 */
public class Pattern {

    public static void main(String args[]) throws IOException, 
                               ClassNotFoundException {
        Shoe whiteNikeShoe = new Shoe("Nike", 1000, 9, "WHITE", true);
        System.out.println("Before Serialization");
        whiteNikeShoe.print();

        // serializing shoe object
        writeShoe(whiteNikeShoe);

        // creating another Shoe with different brand
        Shoe blackAdidasShoe = new Shoe("Adidas", 2000, 8, "Black", true);
        
        
        // deserializing shoe object
        whiteNikeShoe = (Shoe) readShoe();

        System.out.println("After DeSerialization");
        whiteNikeShoe.print();
    }

    private static void writeShoe(Serializable shoe) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(
                        new FileOutputStream(new File("shoe.ser")));
        oos.writeObject(shoe);
        oos.close();
    }

    private static Object readShoe() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(
                        new FileInputStream(new File("shoe.ser")));
        Object obj = ois.readObject();
        return obj;
    }

}

class Shoe implements Serializable {

    // static final variable
    private static final long serialVersionUID = 40L;
    private static final Logger logger = Logger.getLogger(Shoe.class.getName());

    // static variable but not final
    private static String _brand;

    // instance variable
    private int _id;
    private int _size;
    private String _color;

    // transient variable
    private transient boolean _isRunningShoe;

    // non serializable field
    Thread _thread;

    public Shoe(String brand, int id, int size, String color, 
                                      boolean isRunningShoe) {
        System.out.println("Inside Constructor");
        _brand = brand;
        _id = id;
        _size = size;
        _color = color;
        _isRunningShoe = isRunningShoe;

    }

    public String brand() {
        return _brand;
    }

    public int id() {
        return _id;
    }

    public int size() {
        return _size;
    }

    public String color() {
        return _color;
    }

    public void print() {
        System.out.println("SerialVersionUID (final static field) : "
                            + serialVersionUID);
        System.out.println("logger ((final static field) : " + logger);
        System.out.println("_brand (static field) : " + _brand);
        System.out.println("_id (instance variable) : " + _id);
        System.out.println("_size (instance variable) : " + _size);
        System.out.println("_color (instance variable) : " + _color);
        System.out.println("_isRunningShoed (transient variable) : " 
                              + _isRunningShoe);
        System.out.println("_thread (non-serializable field) : " + _thread);

    }

}

Output
Inside Constructor
Before Serialization
SerialVersionUID (final static field) : 40
logger ((final static field) : java.util.logging.Logger@42aab87f
_brand (static field) : Nike
_id (instance variable) : 1000
_size (instance variable) : 9
_color (instance variable) : WHITE
_isRunningShoed (transient variable) : true
_thread (non-serializable field) : null
Inside Constructor
After DeSerialization
SerialVersionUID (final static field) : 40
logger ((final static field) : java.util.logging.Logger@42aab87f
_brand (static field) : Adidas
_id (instance variable) : 1000
_size (instance variable) : 9
_color (instance variable) : WHITE
_isRunningShoed (transient variable) : false
_thread (non-serializable field) : null

A picture is worth a thousand words, so here is a diagram that explains the serialization and deserialization process from a 10K feet view:

How to Serialize Object in Java - Serialization Example



Observation and Explanation

Now let's try to understand what happened when we serialize an instance of Shoe and later when we de-serialized it. I have created different types of variables in this class to show that whether their value is persisted or restored during Serialization or not. 

In the Shoe class, we have two static final fields, SerialVersionUID, and Logger; their values are not persisted but because they are static. They are also initialized at the time of class loading, so they are fine. Also, because they are final, there is no danger of someone changing their value.

The SerialVersionUID is a very important field for a serializable class and you should always define it. If you don't define then JVM will calculate this value by reading the structure of the class e.g. number of instance variables, their types, etc. 

This means next time you add another instance variable or you remove the old one, you risk getting a different SerialVersionUID and if that happens you won't be able to restore the object saved by the previous version of your program.

This has actually happened to us when one of the developers accidentally removed the SerialVersionUID from one of the user preferences classes and when the user downloads and run the new version of our Java application, his preferences were all gone. 

He was a trader and for them, their preferences mean a lot, it was a hard time to console and pacify him before we reverted back his GUI to the previous version and restored his preferences. I am sure, you would never want to upset your clients and user.

Serialization is full of such details and I highly recommend reading Joshua Bloch's advice on Effective Java related to Serialization before implementing it in your real-world project. Those are invaluable pieces of advice and key to successfully and correctly implement Serliazable in anything other than a demo program like this one.

Serialization best practices in Java


Now let's come to a simple, non-final static variable, its value is also not persisted during Serialization, that's why you see _brand=Nike before Serialization and _brand=Adidas after. 

Do you know why it happened? because when we created another instance of Shoe for Adidas, we reset the value of this variable to "Adidas", and since the constructor is not called during deserialization, and the class is not loaded again, its value remains "Adidas". It could have been null if de-serialization would have taken place at another JVM instance.

Now let's see our three instance variables _id, _size, and _color, their values are persisted during serialization and are restored during de-serialization. This is why we see correct values for these three fields. Next is our transient variable isRunningShoe, this is a tricky one, the value of a transient variable is not stored during Serialization and that's why the value of isRunningShoe is incorrect after de-serialization.

It was true before but it became false after de-serialization. You would not have noticed this had that boolean variable was initialized as false, and you would have thought that the value of the transient variable was saved and restored, which is not true. So beware of default values during Serialization, variables like static and transient will be initialized to their default value after de-serialization.

Last is our non-serializable instance variable _thread, which holds the instance of java.lang.Thread, which doesn't implement the Serializable interface. It's really interesting that the default Serialization process doesn't complain about this variable, this can also be tricky to understand if don't pay enough attention. The reason was that the variable didn't hold any value.

Now just initialize that variable as Thread _thread = new Thread() and re-run the program again. This time will get the following Exception :

Exception in thread "main" java.io.NotSerializableException: java.lang.Thread
 at java.io.ObjectOutputStream.writeObject0(Unknown Source)
 at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)
 at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
 at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
 at java.io.ObjectOutputStream.writeObject0(Unknown Source)
 at java.io.ObjectOutputStream.writeObject(Unknown Source)
 at SerializationDemo.writeShoe(HelloHP.java:42)
 at SerializationDemo.main(HelloHP.java:28)

Because Thread doesn't implement a Serializable interface, you can not serialize it. This is a very common problem during the maintenance of a legacy Java project. Suppose you have a Serializable class Employee, and later one developer introduced another instance variable Department, which is not Serializable. Do you know what will happen? The Employee can not be serialized anymore.

That's why I recommend putting a Serialization alert in the source file of a Serializable class, reminding them about not adding any variable which is not serializable or making it transient if they really need it. You can further see Java Coding Guidelines for more coding best practices while writing Java applications. It contains 75 recommendations for reliable and secure Java programs.

In short, we can say that :
  • The value of the static variable is not persisted during Serialization.
  • The transient variables are not persisted as well.
  • Any NonSerializable field, which is not static or transient will break the Serialization process by throwing ava.io.NotSerializableException.
  • The constructor of a serializable class is not called during Serialization.

That's all about how to serialize an object in Java using a Serializable interface. We have seen both saving and restoring objects using serialization and de-serialization and also explored some key concepts related to how serialization works in Java.

 For example, transient and static variables are not saved during serialization, if you declare a static variable, make sure it's not final, and remember that constructor is not called during the de-serialization process.

All these concepts are very important to implement serialization correctly in your program. You can further read Effective Java by Joshua Bloch to understand effective serialization e.g. with custom binary formats.  All the items related to Serialization in this book are a must-read for any serious Java developer.



Other Java serialization tutorials you may like to explore
  • What Every Java developer should know about Serialization (read)
  • Difference between Serializable and Externalizable in Java? (answer)
  • Why should you use SerialVersionUID in Java? (answer)
  • Google Protocol Buffer - a fast alternative of serialization in Java? (tutorial)
  • Difference between transient and volatile variables in Java? (answer)
  • How to use a transient variable in the Serializable class? (answer)

Thanks for reading this article so far. If you like this Java Serialization tutorial land explanation then please share it with your friends dn colleagues. If you have any questions or feedback please drop a note. 

4 comments:

  1. Nice Article. It will be of great help if you can explain Externalization also in the same manner.

    ReplyDelete
  2. Hi,
    I think there is Typo Error in below Statement:
    The constructor of serializable class is not called during Deserialization(Not Serialization).

    Just to avoid confusion for Readers. Anyways Nice Post.

    ReplyDelete
  3. Externalizable interface is not a marker interface. Kindly update the post

    ReplyDelete