Managing several versions of serialized Java objects
java, serialization
Solution
Lets assume that it's (logically) possible to convert an old object to a new object using some clever strategy to set nonexistant fields etc etc... How do I arrange my source code?
I see two ways of handling this. First off, you should never change the `serialVersionUID` unless you want `InvalidClassException` to be thrown. The second rule is to not change the types of fields but to only add or remove fields which serialization handles automagically. For example, if a serialized data has the version of the class which has `boolean sharpTeeth;` but the class doesn't have that field then it will be ignored during deserialization. If the class has the `sharpTeeth` field but the serialized data doesn't then it will get initialized to its default value – `false` in this case.
This is especially important with distributed systems where you want to try to handle both forwards and backwards compatibility. You don't want to upgrade a version of application A and break another application B which depends on A. By not changing the `serialVersionUID` but just adding or removing fields you can do that. Later versions of your entity need to support older versions without values in newer fields but older entities won't mind if new fields are available. This also means that you shouldn't change a field's scale as well.
Serialization is pretty smart but it does not handle type changes to fields. You shouldn't just change `paws` from an `int` to a `long`. Instead, I'd recommend adding a `long pawsLong` or some such and writing your code to handle the possibility of there being `int paws` or `long pawsLong` having a value.
public long getPaws() {
if (pawsLong > 0) {
return pawsLong;
} else {
// paws used to be an integer
return paws;
}
}
You could also write your own `readObject` method to do the conversion at de-serialization time:
private void readObject(java.io.ObjectInputStream in) {
super.readObject(in);
// paws used to be an integer
if (pawsLong == 0 && paws != 0) {
pawsLong = paws;
}
}
If this doesn't work for you then custom serialization is the way to go. You have to start from the beginning doing this however and define custom `readObject(...)` and `writeObject(...)` methods with an internal version id. Something like:
// never change this
private static final long serialVersionUID = 3375159358757648792L;
// only goes up
private static final int INTERNAL_VERSION_ID = 2;
...
// NOTE: in version #1, this was an int
private long paws;
private void readObject(java.io.ObjectInputStream in) {
int version = in.readInt();
switch (version) {
case 1 :
paws = in.readInt();
...
case 2 :
paws = in.readLong();
...
private void writeObject(java.io.ObjectOutputStream out) {
out.writeInt(INTERNAL_VERSION_ID);
out.writeLong(paws);
...
But this method does not help you with forwards compatibility. A version 1 reader won't understand version 2 serialization input.
Should I do deserialization in one class loader, and if that fails try using another class loader that uses an older version (and so on), or are there better ways?
I would not suggest any of these methods. Sounds very difficult to maintain.
Problem
Lets say that I have a program that for some reason need to handle old versions of serialized objects. Eg: when deserializing, one of these versions may be encountered. ``` class Pet { private static final long serialVersionUID = 1L; int paws; } class Pet { private static final long serialVersionUID = 2L; long paws; // handle marsian centipedes boolean sharpTeeth; } ``` Lets assume that it's (logically) possible to convert an old object to a new object using some clever strategy to set nonexistant fields etc etc, but: How do I arrange my source code? I would probably need both versions in the same source tree when writing a converter, but how do I handle that in , say, eclipse. Should I do deserialization in one class loader, and if that fails try using another class loader that uses an older version (and so on), or are there better ways? What's the best strategy?