Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Upgrading a Java Serializable class

I have read various blogs about Serialization and the use of serialVersionUID. Most of them mention using it to maintain the state of a serializable class.

The scenario I have is;

I know the old serialVersionUID and the new serialVersionUID.

On reading in an object with the old serialVersionUID I want to manipulate the data so it fits the new version, I only want to bother to do this if the object I am reading in is of the old type.

This seems like something that should be very straight forwards!

Is there a way to get hold of the serialVersionUID as the object is read in?

The InvalidClassException is thrown before the readObject method in the serialized class is invoked so I can't access it there.

The only hint I have found is to override the ObjectInputStream so that readClassDescriptor() is available although this seems a heavy weight solution to what must be a common problem!

All help is gratefully received!

M

like image 942
Mr R Avatar asked Feb 15 '11 15:02

Mr R


2 Answers

You should keep the same serialVersionUID. The serialised fields don't have to match those of the class itself. Use ObjectInputStream.readFields and define a serialPersistentFields (although do make sure you spell that correctly).

like image 37
Tom Hawtin - tackline Avatar answered Oct 07 '22 17:10

Tom Hawtin - tackline


I am going to present a few possible ways to support older versions of a class/interface in serialization:

Use Migrators

In such a case you need the following in your Java project:

  1. An immutable interface that unifies all these classes
  2. The old implementation(s) of that interface annotated as @Deprecated
  3. The new implementation of your interface
  4. A Migrator class that helps you convert a deprecated object to a new one

Let me say from the beginning that it's not always possible to work with objects of older versions (see the Oracle documentation on versioning of serializable objects). For the sake of simplicity, let us assume that while upgrading, your classes always implement the interface IEntity that you have defined.

public interface IEntity extends Serializable {
   // your method definitions
}

And assume that you initially work with the class:

public class Entity implements IEntity {
   private static final long serialVersionUID = 123456789L;
   // fields and methods
}

If you need to upgrade the class Entity into a new implementation with a new serialVersionUID, then first annotate it as @Deprecated but don't rename it and don't move it to another package :

@Deprecated 
public class Entity implements IEntity {
private static final long serialVersionUID = 123456789L;
  // fields and methods
}

Now create your new implementation with a new serialVersionUID (important) and an additional constructor as follows...

public class Entity_new implements IEntity {
  private static final long serialVersionUID = 5555558L;

  public Entity_new(IEntity){
     // Create a new instance of Entity_new copying the given IEntity
  }

}

Of course the most critical part of the whole procedure is how you implement the above constructor. If you have serialized some objects of the old type Entity as binary files (using for example ObjectOutputStream) and you have now migrated to Entity_new you can parse them as instances of Entity and then convert them into instances of Entity_new. Here is an example:

public class Migrator {

   private final IEntity entity;
   private Class<? extends IEntity> newestClass = Entity_new.class;

   public Migrator(final IEntity entity){
    this.entity = entity;
   }

   public Migrator setNewestClass(Class<? extends IEntity> clazz){
     this.newestClass = clazz;
     return this;
   }

   public IEntity migrate() throws Exception {
     Constructor<? extends IEntity> constr =  
        newestClass.getConstructor(IEntity.class);
     return constr.newInstance(this.entity);
   }
}

There are of course other alternative that don't require that particular constructor or use java reflection. There are lots of other design approaches one can choose. Note also that exception handling and checks for null objects have been completely omitted for the sake of simplicity in the above code.

Design a generic serializable interface

If applicable, you can try in the first place to design an interface for your class that is not likely to change in the future. If your class needs to store a set of properties that is very likely to be modified, consider using a Map<String, Object> for this purpose, where String refers to the property name/identifier and Object is the corresponding value.

Customize readObject and writeObject

There is yet another way to provide support for older version which I will mention for completeness, but is not one I would choose. You can implement private void readObject(ObjectInputStream in) and private void writeObject(ObjectOutputStream out) in a way that it accommodates both the current and all previous versions of your class/interface. I am not sure whether something like this is always feasible and sustainable and you may end up having a very messy and lengthy implementation of these methods.

Alternative serialization techniques

This doesn't answer the OP's question, but I reckon it is worth bringing it up. You may consider serializing your object in some ASCII format such as JSON, YAML or XML. In such a case, unless you vehemently redesign your serializable interface, extensibility comes out of the box. BSON (binary JSON) is a good choice if you are looking for an extensible binary protocol. Maybe this is the best way to go to provide portability of your objects across software that may not be implemented in Java.

like image 126
2 revs Avatar answered Oct 07 '22 19:10

2 revs