My client has an oracle data base and an object was persisted as a blob field via objOutStream.writeObject, the object now has a different serialVersionUID
(even though the object has no change, maybe different jvm version) and when they try to de-serialize an exception is thrown:
java.io.InvalidClassException: CommissionResult; local class incompatible:
stream classdesc serialVersionUID = 8452040881660460728,
local class serialVersionUID = -5239021592691549158
They didn't assign a fixed value for serialVersionUID
since the beginning so now that some thing changed that exception is thrown. Now they don't want to loose any data, to do so I think the best is to read the objects, de-serialize them, and persist them again via XMLEncoder to avoid future errors like the current "class incompatible" error.
Apparently there are 2 different values for the serialVersionUID
persisted for that object so I want to read the data, try with one value and if it fails then try with the other value, To do so I've tried to change the serialVersionUID
of the class using
the ASM api. I've been able to change the value but the problem is how to make active the change upon the class so when it is de-serialized the objInpStr.readObject()
take my modified version of the class with my specific serializedVersionUID
. I made a test class to simulate the real environment, I take an object (which has as property the object with different serialVersionUID
problem) the object name is Reservation
the property is
CommissionResult
:
public class Reservation implements java.io.Serializable {
private CommissionResult commissionResult = null;
}
public class CommissionResult implements java.io.Serializable{
}
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.commons.SerialVersionUIDAdder;
public class SerialVersionUIDRedefiner extends ClassLoader {
public void workWithFiles() {
try {
Reservation res = new Reservation();
FileOutputStream f = new FileOutputStream("/home/xabstract/tempo/res.ser");
ObjectOutputStream out = new ObjectOutputStream(f);
out.writeObject(res);
out.flush();
out.close();
ClassWriter cw = new ClassWriter(0);
ClassVisitor sv = new SerialVersionUIDAdder(cw); //assigns a real serialVersionUID
ClassVisitor ca = new MyOwnClassAdapter(sv); //asigns my specific serialVerionUID value
ClassReader cr=new ClassReader("Reservation");
cr.accept(ca, 0);
SerialVersionUIDRedefiner loader= new SerialVersionUIDRedefiner();
byte[] code = cw.toByteArray();
Class exampleClass = loader.defineClass("Reservation", code, 0, code.length); //at this point the class Reservation has an especific serialVersionUID value that I put with MyOwnClassAdapter
loader.resolveClass(exampleClass);
loader.loadClass("Reservation");
DeserializerThread dt=new DeserializerThread();
dt.setContextClassLoader(loader);
dt.run();
} catch (Exception e) {
e.printStackTrace();
}}
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class DeserializerThread extends Thread {
public void run() {
try {
FileInputStream f2;
f2 = new FileInputStream("/home/xabstract/tempo/res.ser");
ObjectInputStream in = new ObjectInputStream(f2);
Reservation c1 = (Reservation)in.readObject();
System.out.println(c1);
} catch (Exception e) {
e.printStackTrace();
}
stop();
}
}
MyOwnClassAdapter Relevant code:
public void visitEnd() {
// asign SVUID and add it to the class
try {
cv.visitField(Opcodes.ACC_FINAL + Opcodes.ACC_STATIC,
"serialVersionUID",
"J",
null,
new Long(-11001));//computeSVUID()));
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException("Error while computing SVUID for x"
, e);
}
super.visitEnd();
}
The test should fail with the java.io.InvalidClassException
"local class incompatible"
because I changed the serialVersionUID
after I saved the file and used a new one to read
de file but it doesn't fails so it means that the ObjectInputStream.readObject
is not
using my modified version of the Reservation
class.
Any Ideas? Thanks in advance.
!!!!!!!!!!!!!UPDATE:
Ok, it is possible to redefine the resultClassDescriptor to override the stream serialVersionUID, but, some thing strange happens, as I said before it seems there are 2 versions of the class persisted, objects with serialVersionUID = -5239021592691549158L and others with value 8452040881660460728L this last value is the one generated if I don't specify a value to the local class.
-If I don't specify a value for the serialVersionUID then the default value (8452040881660460728L) is used, but is not possible to de-serealize the objects that has the other value, an error is thrown saying that a property is of an other type.
-If I specify the value -5239021592691549158L then classes persisted with that value are successfully de-serialized, but not the others, same error of types.
this is the error trace :
Potentially Fatal Deserialization Operation. java.io.InvalidClassException: Overriding serialized class version mismatch: local serialVersionUID = -5239021592691549158 stream serialVersionUID = 8452040881660460728 java.lang.ClassCastException: cannot assign instance of java.util.HashMap to field com.posadas.ic.rules.common.commisionRules.CommissionResult.statusCode of type java.lang.String in instance of com.posadas.ic.rules.common.commisionRules.CommissionResult
When this error was thrown the class had the value of -5239021592691549158, if change the value to 8452040881660460728 the class is successfully de-serialized, so, what happens? why is that error that tries to cast for wrong class ?
Thanks
The consequence of not specifying serialVersionUID is that when you add or modify any field in class then the already serialized class will not be able to recover because serialVersionUID generated for a new class and for the old serialized object will be different.
Simply put, we use the serialVersionUID attribute to remember versions of a Serializable class to verify that a loaded class and the serialized object are compatible. The serialVersionUID attributes of different classes are independent. Therefore, it is not necessary for different classes to have unique values.
If class does not contain a serialVersionUID field, its serialVersionUID will be automatically generated by the compiler. Different compilers, or different versions of the same compiler, will generate potentially different values.
The serialization at runtime associates with each serializable class a version number called a serialVersionUID, which is used during deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization.
Jorge I found one solution on http://forums.sun.com/thread.jspa?threadID=518416 which works.
Create the below class in your project. Whereever you creating object of ObjectInputStream, use DecompressibleInputStream instead and it deserializes the old object with the new version Id class.
public class DecompressibleInputStream extends ObjectInputStream {
public DecompressibleInputStream(InputStream in) throws IOException {
super(in);
}
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
ObjectStreamClass resultClassDescriptor = super.readClassDescriptor(); // initially streams descriptor
Class localClass = Class.forName(resultClassDescriptor.getName()); // the class in the local JVM that this descriptor represents.
if (localClass == null) {
System.out.println("No local class for " + resultClassDescriptor.getName());
return resultClassDescriptor;
}
ObjectStreamClass localClassDescriptor = ObjectStreamClass.lookup(localClass);
if (localClassDescriptor != null) { // only if class implements serializable
final long localSUID = localClassDescriptor.getSerialVersionUID();
final long streamSUID = resultClassDescriptor.getSerialVersionUID();
if (streamSUID != localSUID) { // check for serialVersionUID mismatch.
final StringBuffer s = new StringBuffer("Overriding serialized class version mismatch: ");
s.append("local serialVersionUID = ").append(localSUID);
s.append(" stream serialVersionUID = ").append(streamSUID);
Exception e = new InvalidClassException(s.toString());
System.out.println("Potentially Fatal Deserialization Operation. " + e);
resultClassDescriptor = localClassDescriptor; // Use local class descriptor for deserialization
}
}
return resultClassDescriptor;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With