June 29, 2022

Versioning Using serialVersionUID in Java

When a class implements a Serializable interface you would have noticed this warning “The serializable class xxxx does not declare a static final serialVersionUID field of type long”. In this post we’ll see what is this serialVersionUID field and what is its significance with respect to serialization in Java.

InvalidClassException in Java Serialization

To understand the use of serialVersionUID first you’ll have to understand what happens when it is not assigned explicitly.

You have a class that implements Serializable and a class object has been written out to an object stream. While this object is still persisted you make some changes to the class by adding a new field or method. What happens now when you deserialize the serialized object.

It will throw java.io.InvalidClassException because of the changes made to the class.

For example take the following Employee class that implements Serializable interface.

public class Employee implements Serializable{
  private String name;
  private String dept;
  private int salary;
  private transient int ssn;
  Employee(String name, String dept, int salary, int ssn){
    this.name = name;
    this.dept = dept;
    this.salary = salary;
    this.ssn = ssn;
  }
}
Its is serialized using the following class-
public class SerializationDemo {

  public static void main(String[] args) {
    Employee emp = new Employee("Ryan", "IT", 7500, 11111);
    final String fileName = "F:\\knpcode\\emp.ser";
    serialzeObject(emp, fileName);
  }
	
  // Method to serialize object 
  public static void serialzeObject(Object obj, String fileName) { 
    try(ObjectOutputStream outStream = new ObjectOutputStream(new FileOutputStream(fileName))){
      outStream.writeObject(obj); 
    } catch (IOException e) { 
      // TODO Auto-generated
      e.printStackTrace(); 
    } 
  }
}

Once an object is already serialized and persisted to the emp.ser file Employee class is changed to add a new field age.

public class Employee implements Serializable{
  private String name;
  private String dept;
  private int salary;
  private transient int ssn;
  //new field
  private int age;
  Employee(String name, String dept, int salary, int ssn){
    this.name = name;
    this.dept = dept;
    this.salary = salary;
    this.ssn = ssn;
  }
}

Now when you try to deserialize the already persisted object-

public class SerializationDemo {

  public static void main(String[] args) {
    final String fileName = "F:\\knpcode\\emp.ser";
    /// Do null check here
    Employee e = (Employee)deSerializeObject(fileName);
    System.out.println("Name- " + e.getName());
    System.out.println("Dept- " + e.getDept());
    System.out.println("Salary- " + e.getSalary());
    System.out.println("SSN- " + e.getSsn());
  }
	
  // Method to deserialize object
  public static Object deSerializeObject(String fileName){
    Object obj = null;
    try(ObjectInputStream inStream = new ObjectInputStream(new FileInputStream(fileName))){
      obj = inStream.readObject();	 			
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (ClassNotFoundException e) {
      System.out.println("No class found for serialization");
      e.printStackTrace();
    }
    return obj;
  }
}

Exception is thrown-

java.io.InvalidClassException: com.knpcode.proj.Programs.Employee; local class incompatible: stream classdesc serialVersionUID = -3183131693238108702, local class serialVersionUID = 7612606573038921492
	at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:689)
	at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1903)
	at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1772)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2060)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1594)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:430)
	at com.knpcode.proj.Programs.SerializationDemo.deSerializeObject(SerializationDemo.java:39)
	at com.knpcode.proj.Programs.SerializationDemo.main(SerializationDemo.java:16)

If you notice the stack trace there is a message-

local class incompatible: stream classdesc serialVersionUID = -3183131693238108702, local class serialVersionUID = 7612606573038921492

Clearly object when serialized and the class which it refers to are not compatible any more and the serialVersionUID of the serialized object and the changed class differ. Now, the question is who assigns this ID?

Version control using serialVersionUID

Classes that implement Serializable interface are automatically given a unique identifier. If the identifier of the class does not equal the identifier of the serialized object, the exception will be thrown.

What if you want to control that default action so that exception is not thrown for changes in the class but default values are used instead for the newly added fields? That’s where serialVersionUID comes handy.

Unique identifier that is part of all classes is maintained in a field called serialVersionUID. If you want to control class versioning you will have to assign serialVersionUID yourself. Then the ID remains same pre and post serialization.

When you assign serialVersionUID yourself you have the flexibility of controlling class versioning.

  • If you don't change the serialVersionUID after updating the class then object deserialization happens with out throwing any exception.
  • If you think that the changes made to the class are significant then you can change the serialVersionUID assigned to the class. Because of the non-matching serialVersionUID, InvalidClassException will be thrown now.

IDE like Eclipse gives you an option to generate serialVersionUID or you can use serialver utility that comes with the JDK distribution to generate serialVersionUID.

For example generating serialVersionUID for com.knpcode.proj.Programs.Employee using serialver utility class.

F:\Anshu\NetJs\Programs\target\classes>serialver com.knpcode.proj.Programs.Employee
com.knpcode.proj.Programs.Employee:    private static final long serialVersionUID = 7612606573038921492L;

serialVersionUID Java example

In the previous example we already used the Employee class now serialVersionUID is added to the class using the "Add generated serial version id" option in Eclipse IDE.

public class Employee implements Serializable{
  private static final long serialVersionUID = 7612606573038921492L;
  private String name;
  private String dept;
  private int salary;
  private transient int ssn;
  Employee(String name, String dept, int salary, int ssn){
    this.name = name;
    this.dept = dept;
    this.salary = salary;
    this.ssn = ssn;
  }
}

With serialVersionUID added, now if you follow the same tasks of serializing an object then changing the class by adding a new field (but not changing the added serialVersionUID) and then deserializing the object, you should get the default value for the new field rather than getting an exception.

Name- Ryan
Dept- IT
Salary- 7500
SSN- 0

That's all for the topic Versioning Using serialVersionUID in Java. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment