Wednesday, May 28, 2014

serialVersionUID


When you serialize an object using Serialization mechanism (by implementing Serializable interface), there is a possibility that you may face versioning issues and because of these versioning issues, you will not be able to deserialize the object.



Sections in this post: 
  1. Problem to face
  2. Root of the problem
  3. Solution
  4. What is serialVersionUID
  5. Example
  6. When to update serialVersionUID
    1. Compatible changes
    2. Incompatible changes


Problem scenario....

lets say you created a class, instantiated it, and wrote it out to an object stream. That flattened object sits in the file system for some time. Meanwhile, you update the class file, perhaps adding a new field. Now try to read the flattened object. An exception "java.io.InvalidClassException" will be thrown.

 Root of the problem

lets first see what is actually causing this problem? Why should any change in a serialized class throw "InvalidClassException". During object serialization, the default Java serialization mechanism writes the metadata about the object, which includes the class name, field names and types, and superclass. All this information is stored as part of the serialized object.
When you deserialize the object, this information is read to reconstitute the object. But to perform the deserialization, the object needs to be identified first and this will be done by serialVersionUID. So everytime an object is serialized the java serialization mechanism automatically computes a hash value using ObjectStreamClass’s computeSerialVersionUID() method by passing the class name, sorted member names, modifiers, and interfaces to the secure hash algorithm (SHA), which returns a hash value, the serialVersionUID.

Now when the serilaized object is retrieved, the JVM first evaluates the serialVersionUID of the serialized class and compares the serialVersionUID value with the one of the object. If the sserialVersionUID values match then the object is said to be compatible with the class and hence it is de-serialized. If not InvalidClassException exception is thrown.

 And the solution is...

The solution is very simple. Instead of relying on the JVM to generate the serialVersionUID, you explicitly mention (generate) the serialVersionUID in your class. The syntax is:


 private final static long serialVersionUID = <integer value> 


What is serialVersionUID? 

Its a static, private variable in the class. Once you define the serialVersionUID in your class explicitly, you don't need to update it until and unless you make the incompatible changes.
 
Example:

Consider the same example taken from serialization post to explain the issue and importance of maintaining serialVersionUID.

 

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;

public class MyDateObject implements Serializable{
  
    //first time we keep serial VersionUID as 1L
    private static final long serialVersionUID = 1L;
    private Date date;

    public MyDateObject() {
        //date= Calendar.getInstance().getTime();
         calculateCurrentTime();
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date=date;
     }
   
    private void calculateCurrentTime(){
        date = Calendar.getInstance().getTime();
    }
   
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
    }

    private void readObject(ObjectInputStream in)
        throws IOException, ClassNotFoundException{

        // our "pseudo-constructor"
        in.defaultReadObject();
        // now perfrom same operation you need to do in constructor
        calculateCurrentTime();
    }
}

 Class to serialize MayDate object :



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

//Class to persist the time in a flat file time.ser
public class WriteSerialClass {

  public static void main(String [] args) {
      String filename = "c://time.txt";

      if(args.length > 0){
          filename = args[0];
      }
      
      MyDateObject time = new MyDateObject();
      FileOutputStream fos = null;
      ObjectOutputStream out = null;

      try{
          fos = new FileOutputStream(filename);
          out = new ObjectOutputStream(fos);
          out.writeObject(time);
          out.close();
      }catch(IOException ex){
          ex.printStackTrace();
      }
   }

Class to De-serialize MydateObject:


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

public class ReadSerialClass   {

     public static void main(String [] args) {
            String filename = "c://time.txt";

            if(args.length > 0){
                filename = args[0];
            }
           
            MyDateObject time = null;
            FileInputStream fis = null;
            ObjectInputStream in = null;

            try{
                fis = new FileInputStream(filename);
                in = new ObjectInputStream(fis);
                time = (MyDateObject)in.readObject();
                in.close();
            }catch(IOException ex){
                ex.printStackTrace();
            }catch(ClassNotFoundException cnfe){
                cnfe.printStackTrace();
            }

            // print out restored time
            System.out.println("Restored time: " + time.getDate());

            // print out the current time
            System.out.println("Current time: "
                + Calendar.getInstance().getTime());

         }
     } 
Output:
=======================================================
Restored time: Wed May 28 18:11:41 IST 2014
Current time: Wed May 28 18:11:42 IST 2014
 
 

 Now run the following program again by changing serialVersionUID value in myDateObject class:


 
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;

public class MyDateObject implements Serializable{
  
   //Now we change serial VersionUID as 2L
    private static final long serialVersionUID = 2L;
    private Date date;

    public MyDateObject() {
        //date= Calendar.getInstance().getTime();
         calculateCurrentTime();
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date=date;
     }
   
    private void calculateCurrentTime(){
        date = Calendar.getInstance().getTime();
    }
   
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
    }

    private void readObject(ObjectInputStream in)
        throws IOException, ClassNotFoundException{

        // our "pseudo-constructor"
        in.defaultReadObject();
        // now perfrom same operation you need to do in constructor
        calculateCurrentTime();
    }
}
Output:
=========================================================
java.io.InvalidClassException: com.MyDateObject; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
    at java.io.ObjectStreamClass.initNonProxy(Unknown Source)
    at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
    at java.io.ObjectInputStream.readClassDesc(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.readObject(Unknown Source) 

 The reason of the above error is the version change and exactly this is the reason for maintaining the version.
By maintaining version we keep the serialization/de-serialiation consistent

When to update serialVersionUID?

Adding serialVersinUID manually to the class does not mean that it should never be updated and never need not be updated. There is no need to update the serialVersionUID if the change in the class is compatible but it should be updated if the change is incompatible

Some of compatible changes are:
  • Adding field.
  • Adding classes.
  • Removing classes.
  • Adding writeObject/readObject methods.
  • Removing writeObject/readObject methods.
  • Adding java.io.Serializable.
  • Changing the access to a field.
  • Changing a field from static to nonstatic or transient to nontransient.
  Some of the Incompatible changes  are:
  • Deleting fields.
  • Moving classes up or down the hierarchy.
  • Changing a nonstatic field to static or a nontransient field to transient.
  • Changing the declared type of a primitive field.
  • Changing the writeObject or readObject method.
  • Changing a class from Serializable to Externalizable or visa-versa.
  • Removing either Serializable or Externalizable. 
  • Adding the writeReplace or readResolve method. 


No comments :

Post a Comment