Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deal with a Java serialized object whose package changed?

I have a Java class that is stored in an HttpSession object that's serialized and transfered between servers in a cluster environment. For the purpose of this explanation, lets call this class "Person".

While in the process of improving the code, this class was moved from "com.acme.Person" to "com.acme.entity.Person". Internally, the class remains exactly the same (same fields, same methods, same everything).

The problem is that we have two sets of servers running the old code and the new code at the same time. The servers with the old code have serialized HttpSession object and when the new code unserializes it, it throws a ClassNotFoundException because it can't find the old reference to com.acme.Person. At this point, it's easy to deal with this because we can just recreate the object using the new package. The problem then becomes that the HttpSession in the new servers, will serialize the object with the new reference to com.acme.entity.Person, and when this is unserialized in the servers running the old code, another exception will be thrown. At this point, we can't deal with this exception anymore.

What's the best strategy to follow for this kind of cases? Is there a way to tell the new servers to serialize the object with the reference to the old package and unserialize references to the old package to the new one? How would we transition to using the new package and forgetting about the old one once all servers run the new code?

like image 351
Alejandro Avatar asked Mar 14 '11 22:03

Alejandro


3 Answers

I found this blog post that claims to have a solution, though it doesn't spell it out very clearly.

What it is actually saying is that you create a subclass of ObjectInputStream that overrides the readClassDescriptor method to do something like this:

@Override
protected java.io.ObjectStreamClass readClassDescriptor() 
        throws IOException, ClassNotFoundException {
    ObjectStreamClass desc = super.readClassDescriptor();
    if (desc.getName().equals("oldpkg.Widget")) {
        return ObjectStreamClass.lookup(newpkg.Widget.class);
    }
    return desc;
};

You should also look at this SO question and its answers which cover some of the same ground as your question.

My advice would be: don't support the case where old versions of software read data serialized by the new version.

  • This is a good opportunity to encourage (actually force) people to upgrade to the latest version of the code-base. Generally speaking, it is in everyone's interest that this happen sooner rather than later.

  • If it is premature to force people to upgrade for other reasons, then (IMO) you should seriously consider backing out your changes to the class / package names. Wait until you've got a clear strategy / plan for upgrading that is 1) technically sound, and 2) acceptable to all stakeholders.

like image 58
Stephen C Avatar answered Oct 23 '22 04:10

Stephen C


This is always a big headache with Java serialization. As long as you're migrating your classes anyway, I'd recommend looking into migrating toward a different serialization mechanism like XStream. There's an interesting article on this at JavaLobby.

like image 3
Ted Hopp Avatar answered Oct 23 '22 04:10

Ted Hopp


In some cases you don't have access to the ObjectInputStream, and you can't override readClassDescriptor(). For example, another subsystem serializes and deserializes these objects. In our case, we had legacy instances serialized in Quartz job data maps.

For this case, you have to maintain a shallow definition of the old class. You can implement the old class's readResolve() and writeReplace() methods.

class OldClass{
  private int aField;
  private Object readResolve() throws ObjectStreamException {
     return new NewClass(aField);
  }
  private Object writeReplace() throws ObjectStreamException {
     return new NewClass(aField);
  }
}
like image 2
hughw Avatar answered Oct 23 '22 04:10

hughw