Wednesday, November 23, 2022

SerialVersionUID And Versioning in Java Serialization

If you ever implemented Serializable interface in Java then you would have seen the warning “The serializable class XXX does not declare a static final serialVersionUID field of type long”. If you ever wondered why this warning about serialVersionUID in your Java code then this post will help to understand it.


SerialVersionUID and versioning

A simple explanation for why do we need to declare serialVersionUID is; it helps with versioning.

Suppose you have some class which is serialized and it changes before it is deserialized. You will need to consider what happens in that situation? Can you allow the new version of your class to read old data.

To help with these versioning scenarios serialization process in Java provides a simple versioning mechanism using serialVersionUID.

Generating serialVersionUID

The stream-unique identifier is a 64-bit hash of the class name, interface class names, methods, and fields. If you are using IDE like eclipse and you have a class that implements Serializable interface then you will get a warning upfront that serialVersionUID is not declared.

Eclipse IDE also gives you options-

  • to add default serialVersionUID
  • OR
  • to add generated serialVersionUID

In case you choose to ignore that warning even then by default serialization mechanism in Java will generate serialVersionUID, both the name of the class and its serialVersionUID are written to the object stream.

During deserialization again serialVersionUID will be generated and compared with the previously written serialVersionUID, if there is a mismatch that means version is changed and InvalidClassException will be thrown.

SerialVersionUID Java example

Let’s try to clear it with an example. Suppose you have a Person class with few fields and you serialize the Person class. Before you try to deserialize the serialized Person object, you add a new field to the Person class.

So here is a Person class with fields like id, name and age. It implements Serializable interface and choose to ignore the warning to declare serialVersionUID.

public class Person implements Serializable{
 private String name;
 private int id;
 private int age;
 // Constructor
 Person(String name, int id, int age){
   System.out.println("In Constructor with args");
   this.name = name;
   this.id = id;
   this.age = age; 
 }
 // no-arg Constructor
 Person(){
   System.out.println("no-arg constructor");
 }
 
 public String getName() {
  return name;
 }
  
 public int getAge() {
  return age;
 }
 public int getId() {
  return id;
 }
}

Util class

This is a class with static methods to serialize and deserialize.

public class Util {
  /**
   * Method used for serialization
   * @param obj
   * @param fileName
   */
  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
   * @param fileName
   * @return
   * @throws ClassNotFoundException
   */
  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;
  }
}

Now I serialize Person class object using the following test class.

public class SerializationDemo {

 public static void main(String[] args) {
  // Creating and initializaing a Person object
    Person person = new Person("User1", 1, 22);
    // file name
    final String fileName = "F://person.ser";
    // serializing
    Util.serialzeObject(person, fileName);
    
    /*try {
     // deserializing
     person = (Person)Util.deSerialzeObject(fileName);
    } catch (ClassNotFoundException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }*/
 }
}

So far so good, Person class object is created and serialized. Now add a new field city to Person class.

public class Person implements Serializable{
 private String name;
 private int id;
 private int age;
 private String city;
 
 // Constructor
 Person(String name, int id, int age){
   System.out.println("In Constructor with args");
   this.name = name;
   this.id = id;
   this.age = age; 
 }
 // no-arg Constructor
 Person(){
   System.out.println("no-arg constructor");
 }
 
 public String getName() {
  return name;
 }
  
 
 public int getAge() {
  return age;
 }
 public int getId() {
  return id;
 }
 public String getCity() {
  return city;
 }
}

Now if I try to deserialize the byte stream which was already created before the inclusion of this new field in Person class InvalidClassException will be thrown.

public class SerializationDemo {
  public static void main(String[] args) {
    // Creating and initializaing a Person object
    Person person = new Person("User1", 1, 22);
    // file name
    final String fileName = "F://person.ser";
    // serializing
    //Util.serialzeObject(person, fileName);
    
    try {
      // deserializing
      person = (Person)Util.deSerialzeObject(fileName);
    } catch (ClassNotFoundException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}
java.io.InvalidClassException: org.netjs.prog.Person; local class incompatible: stream classdesc 
serialVersionUID = -4901887311122736183, local class serialVersionUID = -1818819755742473032
 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)
 at org.netjs.prog.Util.deSerialzeObject(Util.java:39)
 at org.netjs.prog.SerializationDemo.main(SerializationDemo.java:15)

Here note that even if you chose to ignore the warning and didn’t declare the serialVersionUID it is still generated. You can see that 2 different serialVersionUIDs are there as the class has changed and that mismatch has caused the InvalidClassException.

Assigning serialVersionUID to Java class

As shown in the above example if you choose to ignore the warning and rely on generation of serialVersionUID by the serialization mechanism itself it will always fail if there is a change in the class.

That’s why you as an implementor of the class should take charge and assign a serialVersionUID yourself (If you are using IDE like Eclipse that can be generated by Eclipse for you or you can use serialver tool which comes with the JDK to generate serialVersionUID).

With you taking charge when you know your class has changed in a way that it is not compatible with the old version anymore you can choose to change the serialVersionUID. In that case during deserialization because of the non-matching serialVersionUID, InvalidClassException will be thrown.

If you choose not to change the serialVersionUID even if your class has changed as you think the change is not significant then deserialization will proceed with out throwing any exception.

Let’s take the same example as above but this time serialVersionUID is declared.

Person class

public class Person implements Serializable{
 private static final long serialVersionUID = -4046333379855427853L;
 private String name;
 private int id;
 private int age;
 /*private String city;
 public String getCity() {
  return city;
 }*/
 // Constructor
 Person(String name, int id, int age){
   System.out.println("In Constructor with args");
   this.name = name;
   this.id = id;
   this.age = age; 
 }
 // no-arg Constructor
 Person(){
   System.out.println("no-arg constructor");
 }
 
 public String getName() {
  return name;
 }
   
 public int getAge() {
  return age;
 }
 public int getId() {
  return id;
 }
}

Now the class is serialized.

public class SerializationDemo {

 public static void main(String[] args) {
  // Creating and initializaing a Person object
    Person person = new Person("User1", 1, 22);
    // file name
    final String fileName = "F://person.ser";
    // serializing
    //Util.serialzeObject(person, fileName);
    
    /*try {
     // deserializing
     person = (Person)Util.deSerialzeObject(fileName);
     System.out.println("id " + person.getId() + " Name "+ person.getName() 
       + " Age " + person.getAge() + " City " + person.getCity());
    } catch (ClassNotFoundException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }*/
 }
}

Now a new field city is added in the Person class but the serialVersionUID remains same as before.

public class Person implements Serializable{

 private static final long serialVersionUID = -4046333379855427853L;
 private String name;
 private int id;
 private int age;
 private String city;
 public String getCity() {
  return city;
 }
 // Constructor
 Person(String name, int id, int age){
   System.out.println("In Constructor with args");
   this.name = name;
   this.id = id;
   this.age = age; 
 }
 // no-arg Constructor
 Person(){
   System.out.println("no-arg constructor");
 }
 
 public String getName() {
  return name;
 }
   
 public int getAge() {
  return age;
 }
 public int getId() {
  return id;
 }
}

Now deserialization will happen though there won’t be any value for the city field.

public class SerializationDemo {

 public static void main(String[] args) {
  // Creating and initializaing a Person object
    Person person = new Person("User1", 1, 22);
    // file name
    final String fileName = "F://person.ser";
    // serializing
    //Util.serialzeObject(person, fileName);
    
    try {
     // deserializing
     person = (Person)Util.deSerialzeObject(fileName);
     System.out.println("id " + person.getId() + " Name "+ person.getName() 
       + " Age " + person.getAge() + " City " + person.getCity());
    } catch (ClassNotFoundException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }
 }
}

Output

In Constructor with args
id 1 Name User1 Age 22 City null

For test you can regenerate the serialVersionUID of the Person class after adding city field. In that case deserialization will fail as there will be a mismatch between serialVersionUIDs.

Points to remember

  1. serialVersionUID is used for versioning of the serialized streams. During serialization process serialVersionUID is also stored. During deserialization generated serialVersionUID is matched with the stored one and if there is a mismatch process fails.
  2. serialVersionUID is a 64-bit hash of the class name, interface class names, methods, and fields. If you don’t declare one yourself serialization process will still generate serialVersionUID. In that case it will fail for any change in the class.
  3. If you declare the serialVersionUID that gives you control over the versioning. When you think class has grown in way that is not compatible with the previous versions then you can change the serialVersionUID. If you think change in the class are not significant enough to change the serialVersionUID you may choose to retain the same serialVersionUID. In that case serialization and deserialization will not fail even if your class had changed.
  4. serialVersionUID is declared as a private static final long and it is always better to declare one in order to have control over the versioning of the class.

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

>>>Return to Java Advanced Tutorial Page


Related Topics

  1. Serialization and Deserialization in Java
  2. Transient Keyword in Java With Examples
  3. Externalizable Interface in Java
  4. Serialization Proxy Pattern in Java
  5. Marker interface in Java

You may also like-

  1. How HashSet Works Internally in Java
  2. Fail-Fast Vs Fail-Safe Iterator in Java
  3. Java Phaser With Examples
  4. Java ReentrantReadWriteLock With Examples
  5. Java Stream API Tutorial
  6. Lambda Expressions in Java 8
  7. Type Wrapper Classes in Java
  8. Try-With-Resources in Java With Examples