Java Fundamental Classes Reference

Previous Chapter 7
Object Serialization
Next
 

7.3 Versioning of Classes

One you have written a class that works with serialization, the next concern is that serialized instances of that class can be deserialized by programs that use a different version of the same class.

After a class is written, it is often necessary to modify its definition as requirements change or new features are needed. Deserialization may fail if the definition of a class in use when an instance was serialized is different than the definition in use when the instance is deserialized. If you do not take any measures to assure the serialization mechanism that the two classes are different versions of the same class, deserialization fails by throwing an InvalidClassException. And even if the serialization mechanism is satisfied that the two class definitions represent different versions of the same class, it may find incompatible differences between the definitions.

The following changes to the definition of a class are noticed by the serialization mechanism:

It's possible to code around some of these problems if you can first convince the serialization mechanism that the two class definitions are different versions of the same class. In order to convince the serialization mechanism of such a thing, the class definition used for deserialization of an object must define a static final long variable named serialVersionUID. If the class used for serialization also defined that variable with the same value, the two class definitions are assumed to define different versions of the same class.

If the class used for serialization does not define serialVersionUID, the serialization mechanism performs the comparison using a value that is computed by calling the ObjectStreamClass.getSerialVersionUID() method. That computation is based on the fields defined by the class. To take advantage of this automatic computation when you define serialVersionUID, you should use the serialver program that comes with the JDK to determine the appropriate value for serialVersionUID. The serialver program computes a value for serialVersionUID by calling the ObjectStreamClass.getSerialVersionUID() method.

Assuming you've convinced the serialization mechanism that the two class definitions represent different versions of the same class, here is some advice on how to deal with the differences that can be worked around:

Missing variables

If the class used to deserialize an object defines variables the class used to serialize the object did not define, the serialized object does not contain any values for those variables. This situation can also arise if the class used to serialize the object defined a variable as static or transient, while the class used to deserialize the object defines it as non-static or non-transient.

When an object is deserialized and there are variables missing in its serialized form, the variables in the deserialized object are set to default values. In other words, the value of such a variable is true if it has an arithmetic data type, false if it has a boolean data type, or null if it has an object reference type. Deserialization ignores intializers in variable declarations.

When you add variables to a Serializable class, consider the possibility that the new version of the class will deserialize an object serialized with an older version of the class. If that happens and it is unacceptable for the new variables to have default values after deserialization, you can define a validateObject() method for the class to check for the default values and provide acceptable values or throw an InvalidObjectException.

Extra variables

If the serialized form of an object contains values for variables that are not defined by the class used to deserialize that object, the values are read and then ignored. If the value of such a variable is an object, the object is created and immediately becomes a candidate for garbage collection.

Missing classes

If the class used to deserialize an object inherits from an ancestor class that the class used to serialize the object did not inherit from, the serialized object does not contain any values for the variables of the additional ancestor class. Just as with missing variables, those variables are deserialized with their default values.

When you add an ancestor class to a Serializable class, consider the possibility that the new version of the class will deserialize an object serialized with an older version of the class. If that happens and it is unacceptable for instance variables in the new ancestor class to have default values after deserialization, you can define a validateObject() method for the class to check for the default values and provide acceptable values or throw an InvalidObjectException.

Extra classes

If the class used to serialize an object inherits from an ancestor class that the class used to deserialize the object does not inherit from, the values for the variables defined by that extra ancestor class are read but not used.

Adding writeObject() and readObject() methods

You can add writeObject() and readObject() methods to a class that did not have them. In order to deserialize objects that were serialized using the older class definition, the new methods must begin by calling defaultWriteObject() and defaultReadObject(). That ensures that information written out using default logic is still processed using default logic.

If the writeObject() and readObject() methods write and read additional information to and from the byte stream, you should also add an additional variable to the class to serve as a version indicator. For example, you might declare an int variable and initialize it to one. If, after defaultReadObject() returns, the value of that variable is 0, you know the object was serialized using the old class definition and that any additional information that would have been written by the writeObject() method will not be there.

Removing writeObject() and readObject() methods

If you remove writeObject() and readObject() methods from a class and deserialize an object using the new class definition, the information written by a call to writeObject() is simply read by the default logic and any additional information is ignored.

Changing a class so that it implements Serializable

If a superclass of an object did not implement Serializable when the object was serialized, and that superclass does implement Serializable when the object is deserialized, the result is similar to the missing class situation. There is no information about the variables of the newly Serializable superclass in the byte stream, so its instance variables are initialized to default values.

Changing a class so that it does not implement Serializable

If a superclass of an object implemented Serializable when the object was serialized, and that superclass does not implement Serializable when the object is deserialized, the result is similar to the extra class situation. The information in the byte stream for that class is read and discarded.


Previous Home Next
Writing Classes to Work with Serialization Book Index Networking

Java in a Nutshell Java Language Reference Java AWT Java Fundamental Classes Exploring Java