4

Say I have class A in server side with two variables.

class A implements Serializable {
    public static final long serialVersionUID = 1234;
    String a;
    String b;
}

In client side I have same class with three variables but same version id.

class A implements Serializable {
    public static final long serialVersionUID = 1234;
    String a;
    String b;
    String c;
}

What happens here? bit confused about its behaviour.

user207421
  • 305,947
  • 44
  • 307
  • 483

1 Answers1

4

This is a stream-compatible change. The extra value will be thrown away if received by the side that doesn't have it, or set to its default value at the side that does have it if sent from the side that doesn't have it.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • What exactly happens , will it thrown away or set to its default value. – Vinay S Jain Apr 24 '18 at 04:33
  • Thrown away (because the class does not have a field for it to go in) in the one direction, set to default (because the stream is missing a value for the field) in the other. – Thilo Apr 24 '18 at 04:34
  • 1
    @VinaySJain I have already answered exactly that question in complete detail. You're just asking me to repeat myself. – user207421 Apr 24 '18 at 04:34
  • Either way, it is much more robust to bump the version number and have code be explicit about what should happen. – Thilo Apr 24 '18 at 04:36
  • @Thilo If you bump the version number the code won't even execute, let alone get a chance to 'be explicit about what should happen'. – user207421 Apr 24 '18 at 04:38
  • @EJP: You would need to provide your own `readObject` and `writeObject` methods. How else would you read in old serialized objects in incompatible formats? https://stackoverflow.com/a/7290812/14955 – Thilo Apr 24 '18 at 04:40
  • 1
    @Thilo No. Your custom `read/writeObject()` methods will never get executed if you bump the `serialVersionUID`. `ObjectInputStream` will throw an [`InvalidClassException`](https://docs.oracle.com/javase/8/docs/api/java/io/InvalidClassException.html). That's why you *shouldn't* bump it. – user207421 Apr 24 '18 at 04:49
  • Might be worth noting that the objects are still read from the stream before being discarded, when the field is absent, so any issues with these objects, like missing classes, may still break the deserialization, even if the field is not there and hence, the object not needed. – Holger Apr 24 '18 at 06:53
  • @Holger That doesn't make sense. If the field is removed from the class, and no other fields use the field's class, the containing class no longer relies on the class of the field. – user207421 Apr 24 '18 at 06:57
  • @EJP maybe you got me wrong. When you deserialize a stream containing an object value for the removed field, that object will be read and an object always has an actual type. If restoring that object fails, it can break the entire operation, regardless of the fact that this object will not be used after restoring. I just tried whether this is still the case and found an interesting behavior with Java 9; it will recover when the obsolete object causes a `ClassNotFoundException`, but will propagate any other failure. This restoring of arbitrary unreferenced objects is a well-known attack vector. – Holger Apr 24 '18 at 07:21
  • @Holger well known.. :| no idea about this – Eugene Apr 24 '18 at 07:43
  • @EJP: So this answer is wrong? https://stackoverflow.com/a/12702699/14955 " The built-in de-serialization mechanism (in.defaultReadObject()) will refuse to de-serialize from old versions of the data. But if you want to you can define your own readObject()-function which can read back old data. This custom code can then check the serialVersionUID in order to know which version the data is in and decide how to de-serialize it. This versioning technique is useful if you store serialized data which survives several versions of your code." – Thilo Apr 24 '18 at 07:52
  • 2
    @Thilo indeed, that answer is dead wrong. It’s not only, as EJP already stated, that you won’t get that far (to the `readObject` method) when the serialVersionUID doesn’t match, there is no way for the `readObject` method to find out, which serialVersionUID the stream’s class descriptor has. But since you normally only get to that point, when the numbers match, there is no need to query that number anyway. You could play stunts with subclasses of `ObjectInputStream`, but that’s an entirely different story… – Holger Apr 24 '18 at 08:55
  • 1
    @Thilo I have to correct myself. If you ever get to an invocation of the `readObject` method, you could find out the stream’s `serialVersionUID`, but for a standard `ObjectInputStream`, you don’t get to that point, if the numbers do not match. – Holger Apr 24 '18 at 09:30
  • @Holger The only sense I can make of all that is that when you say 'deleted' you mean 'inserted'. – user207421 Apr 24 '18 at 11:05
  • @Thilo Yes. It is wrong, and I already clearly stated so in my numerous comments on that answer. Please try what you are suggesting here before you debate this further. – user207421 Apr 24 '18 at 11:13
  • @EJP I said neither, “deleted” nor “inserted”, to avoid that confusion. I said the *stream* contains a value during *deserialization* for a removed field, whereas “removed field” refers to the runtime class. That’s what you described with “*extra value will be thrown away*”, but the interesting point is that the extra value will get deserialized first, including potential invocations of `readObject` and/or `readResolve`, before being thrown away. If still unclear, look at https://ideone.com/ZfOBfk which will fail due to a value for a removed field. Uncomment the `throw` and it will be dropped… – Holger Apr 24 '18 at 14:52