0

This socket will keep receiving some old objects and new objects with same name. However, if I used readObject directly, it will throws fatal exception. Some of us would suggest to ignore old object or change the old object code. This is not possible for me to change client codes. I need to handle the backward-compatibility.


Socket s = ss.accept();
ObjectInputStream lvStream = new ObjectInputStream(s.getInputStream());
Object lvObject = lvStream.readObject();

// The old class but released ( I cannot change it )
public class Apple extends HashMap<String, String> {
    private static final long serialVersionUID = 1L;
}
// The old class but released ( I cannot change it )
public class Apple extends HashMap<String, String> {
    private static final long serialVersionUID = 2L;
}

// The new class in my local ( 1L or 2L still would not solve the issue easily )
public class Apple extends HashMap<String, String> {
    private static final long serialVersionUID = 2L;
}

At the line of readObject(), I got this fatal exception:

java.io.InvalidClassException local class incompatible : stream classdesc serialVersionUID = 1, local class serialVersionUID = 2

How could I read this two kind of Apple objects data (the Hashmap)?

Thank You.

Pika
  • 507
  • 1
  • 6
  • 16
  • 4
    "They are totally same" - no, they're in different packages, according to your comment. The fully-qualified class name is important in serialization (and in general). – Jon Skeet Dec 05 '16 at 07:38
  • Are you asking how to read a serialized object of one class as an instance of the other? – qxz Dec 05 '16 at 07:45
  • 2
    They are two different types. Serial version UID has nothing to do with it. Cast either to `HashMap` and read its entries. – erickson Dec 05 '16 at 07:45
  • @qxz Yes. I just want to read that data. – Pika Dec 05 '16 at 07:50
  • @erickson It can't be casted, because it throws exception at the line of readObject, with no return result. – Pika Dec 05 '16 at 07:51
  • 1
    Are you sure the `serialVersionUID` of the classes haven't changed since you serialized the object? That's what your exception would suggest. – qxz Dec 05 '16 at 07:56
  • That's what your exception *proves*. You have different .class versions at each end. – user207421 Dec 05 '16 at 08:01
  • @qxz In my case, there are two versions class sending to my side. 1L and 2L. And I need to read both object data. – Pika Dec 05 '16 at 08:06
  • So you need to deploy the version of the class that has `serialVersionUID = 2` to the deserializing end. NB Why did you change it? This is usually a Very Bad Idea. – user207421 Dec 05 '16 at 08:39
  • @EJP Sometimes, there are some old codes in client side which i can't change it, but I need to handle the backward compatibility. – Pika Dec 06 '16 at 02:10
  • So you need to *not* change the `serialVersionUID`. That is a prime requirement for compatibility, whatever you may have read or may have been told. – user207421 Dec 06 '16 at 03:58
  • @EJP Theoretically, you are right. However, in reality some kind of old codes are not written by you. For example, if there are two kind of Apple.class in client side which I cannot change, I still have to handle. Of coz, I know this is the supposed practice. But I have no access or modify right to those released codes. Hope you understand my situtation. – Pika Dec 06 '16 at 06:08
  • @AnsonWong You continue to totally and utterly misunderstand what the problem is. It has *nothing whatsoever to do* with 'two kind of Apple.class in client side'. Please read my answer again. – user207421 Dec 06 '16 at 06:51
  • @EJP I had read for quite some times. It provides no solution to my problem. You said deploy the same version. I had claimed for many times that there are more than one old version. When you used version 1, version 2 would be incompatible, vice versa. – Pika Dec 06 '16 at 07:03
  • Everybody here has told you the same thing. Until you assimilate it nobody can help you further. Whatever process has led you into this swamp is *invalid* and needs to be *corrected.* – user207421 Dec 06 '16 at 07:24

2 Answers2

0

Java: how can readObject() read two classes with different serialVersionUID?

Easily. They are different classes. Why shouldn't it?

There are two class with same name but different in serialVersionUID.

So they are different classes. Your question?

I got this fatal exception:

java.io.InvalidClassException local class incompatible : stream classdesc serialVersionUID = 2, local class serialVersionUID = 1

That doesn't have anything whatsoever to do with two classes with the same name in different packages. It means you deployed a version of that class with serialVersionUID = 2 at the sender and a different version with serialVersionUID = 1 at the receiver. Soution: don't do that. Deploy the same version: in this case, the version with 2, as that is what is in the stream. In general you should not change serialVersionUID values without knowing exactly what you are doing.

In my case, there are two versions class sending to my side. 1L and 2L.

No there aren't. There is one version sending, 2L, and a different version receiving, 1L. Don't do that.

user207421
  • 305,947
  • 44
  • 307
  • 483
0

As long as we extend the ObjectInputStream,

we will have access to stream SerialVersionUID to compare with local SerialVersionUID.


Socket s = ss.accept();
// use the extended ObjectInputStream ( DecompressibleInputStream )
DecompressibleInputStreamlvStream = new DecompressibleInputStream(s.getInputStream());
Object lvObject = lvStream.readObject();

import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;

public class DecompressibleInputStream extends ObjectInputStream {

    public DecompressibleInputStream(InputStream in) throws IOException {
        super(in);
    }

    protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
        ObjectStreamClass resultClassDescriptor = super.readClassDescriptor();
        Class<?> localClass;
        try {
            localClass = Class.forName(resultClassDescriptor.getName()); 
        } catch (ClassNotFoundException e) {
            System.err.println("No local class for " + resultClassDescriptor.getName());
            return resultClassDescriptor;
        }
        ObjectStreamClass localClassDescriptor = ObjectStreamClass.lookup(localClass);
        if (localClassDescriptor != null) {
            final long localSUID = localClassDescriptor.getSerialVersionUID();
            final long streamSUID = resultClassDescriptor.getSerialVersionUID();
            if (streamSUID != localSUID) {
                System.err.println("Potentially Fatal Deserialization Operation.");
                resultClassDescriptor = localClassDescriptor;
            }
        }
        return resultClassDescriptor;
    }
}

hope it helps

(credits to this post)

Community
  • 1
  • 1
Pika
  • 507
  • 1
  • 6
  • 16