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.
Problem to face
- Root of the problem
- Solution
- What is serialVersionUID
- Example
- When to update serialVersionUID
- Compatible changes
- 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