matts
matts

Reputation: 6897

How to support backwards-compatible serialization when refactoring a class into an interface in Java?

Unfortunately, the situation in the question title has already happened several years in the past.

I have an Id interface which extends Serializable and contains getters for a name and id number. There is a matching IdImpl class which implements the interface. However, at some point in the past, Id was the class. There is also a serializable container object which has member fields of type Id. These container objects have been being serialized to a database for several years, so there are versions of the container object out there containing both types of Ids. When attempting to deserialize the old objects, we get an InvalidClassException. How can I deserialize the old container objects which contain the old Id concrete class instances?

Full disclosure: a couple of other changes to the Id interface have been made over the years, but I thought they looked like compatible changes (added a field to IdImpl and a getter to Id for "String idType"; generified Comparable). Could one of these changes be causing the problem too?

The classes look something like this:

// current Id interface
public interface Id extends Serializable, Comparable<Id> {
    String getName();
    int getIdNumber();
}

// current Id implementation
public class IdImpl implements Id {
    private static final long serialVersionUID = 10329865109284L;
    private String name;
    private int idNumber;
    IdImpl(String name, int idNumber) { this.name = name; this.idNumber = idNumber; }
    @Override public String getName() { return name; }
    @Override public int getIdNumber() { return idNumber; }
    @Override public int compareTo(Id id) { /* some code here */ }
}

// the container object
public ContainerForm implements Serializable {
    private static final long serialVersionUID = -3294779665912049275L;
    private String someField;
    private Id user;
    private String someOtherField;
    // getters and setters
}

// this is what the _old_ Id concrete class looked like
// (from source control history; not in current project)
public class Id implements Serializable, Comparable {
    // never had a serialVersionUID
    private String name;
    private int idNumber;
    Id(String name, int idNumber) { this.name = name; this.idNumber = idNumber; }
    public String getName() { return name; }
    public int getIdNumber() { return idNumber; }
    public int compareTo(Object id) { /* some code here */ }
}

The exception we get when trying to deserialize container objects is:

java.io.InvalidClassException: mypkg.people.Id; local class incompatible: stream classdesc serialVersionUID = -6494896316839337071, local class serialVersionUID = -869017349143998644
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:562)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1583)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1496)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1732)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
    at java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:1947)
    at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1871)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1753)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1329)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:351)
    at mypkg.forms.FormFactory.getForm(FormFactory.java:3115)
    ... 34 more

Upvotes: 5

Views: 1937

Answers (1)

jtahlborn
jtahlborn

Reputation: 53694

As a general rule, not using serialVersionUID and turning a class into an interface is a very difficult transition to support.

In this specific instance, i believe you could support the old Id implementation by:

  • create a class (call it OldId) which has the old serialVersionUID and old implementation of Id (it should implement Id).
  • create a custom subclass of ObjectInputStream and override the readClassDescriptor() method. call the parent readClassDescriptor and
    • if the returned ObjectStreamClass has the Id class name and the old serialVersionUID, return the ObjectStreamClass for the new OldId class
    • otherwise return the given descriptor

Upvotes: 6

Related Questions