kryo: CompatibleFieldSerializer fails when superclass fields concrete type changes

It looks like CompatibleFieldSerializer is caching the first concrete type for a field, If you try to pass a different concrete type the serializer fails.

I wrote the following code against “com.esotericsoftware:kryo:5.0.0-RC9”

It basically writes a wrapper instance with a int value, then tries to write another wrapper instance with a string value and fails.

public class KryoSerializerDemo {

  public static void main(String[] args) {

    Kryo kryo = new Kryo();

    kryo.setRegistrationRequired(false);
    CompatibleFieldSerializer.CompatibleFieldSerializerConfig config = new CompatibleFieldSerializer.CompatibleFieldSerializerConfig();
    kryo.setDefaultSerializer(new SerializerFactory.CompatibleFieldSerializerFactory(config));

    Output output = new Output(4096, Integer.MAX_VALUE);
    kryo.writeClassAndObject(output, new Wrapper(123));
    output.reset();
    kryo.writeClassAndObject(output, new Wrapper("foo"));
  }

  public static class Wrapper {
    public Wrapper(Object value) {
      this.value = value;
    }

    Object value;
  }
}

When I run the code above, I get the following exception:

Exception in thread "main" com.esotericsoftware.kryo.KryoException: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
Serialization trace:
value (com.foo.KryoSerializerDemo$Wrapper)
	at com.esotericsoftware.kryo.serializers.ReflectField.write(ReflectField.java:96)
	at com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer.write(CompatibleFieldSerializer.java:107)
	at com.esotericsoftware.kryo.Kryo.writeClassAndObject(Kryo.java:640)
	at com.foo.KryoSerializerDemo.main(KryoSerializerDemo.java:26)
Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

	at com.esotericsoftware.kryo.serializers.DefaultSerializers$IntSerializer.write(DefaultSerializers.java:124)
	at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:571)
	at com.esotericsoftware.kryo.serializers.ReflectField.write(ReflectField.java:86)

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 15

Commits related to this issue

Most upvoted comments

@dhofftgt: Great! I’ll do a new release in a couple of days.

@dhofftgt: Thanks for providing the test-case! My test unfortunately didn’t catch this problem because it reads the payload back immediately.

This is a different issue related to #744 and should be relatively easy to fix.

I see serialization seems to work, but I’m seeing some issues on the deserializing side:

public class KryoDeserializerDemo {

  public static void main(String[] args) {

    Kryo kryo = new Kryo();

    kryo.setRegistrationRequired(false);
    CompatibleFieldSerializer.CompatibleFieldSerializerConfig config = new CompatibleFieldSerializer.CompatibleFieldSerializerConfig();
    kryo.setDefaultSerializer(new SerializerFactory.CompatibleFieldSerializerFactory(config));

    Output output1 = new Output(4096, Integer.MAX_VALUE);
    kryo.writeClassAndObject(output1, new Wrapper(123));

    Output output2 = new Output(4096, Integer.MAX_VALUE);
    kryo.writeClassAndObject(output2, new Wrapper("foo"));

    Wrapper wrapper1 = (Wrapper) kryo.readClassAndObject(new Input(output1.getBuffer()));
    Wrapper wrapper2 = (Wrapper) kryo.readClassAndObject(new Input(output2.getBuffer()));

    assert (Integer) wrapper1.value == 123;
    assert wrapper2.value.equals("foo");

  }

  public static class Wrapper {
    public Wrapper() {

    }
    public Wrapper(Object value) {
      this.value = value;
    }

    Object value;
  }
}
Exception in thread "main" com.esotericsoftware.kryo.KryoException: Read type is incompatible with the field type: int -> String (KryoDeserializerDemo$Wrapper#value)
	at com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer.read(CompatibleFieldSerializer.java:170)
	at com.esotericsoftware.kryo.Kryo.readClassAndObject(Kryo.java:813)
	at KryoDeserializerDemo.main(KryoDeserializerDemo.java:23)

@dhofftgt: I just released 5.0.1.

@dhofftgt: I wanted to release 5.0.1 last week, but I’m still lacking some permissions/necessary infos to perform the release. Currently waiting for feedback from @magro.

The problem is that CompatibleFieldSerializer and TaggedFieldSerializer store a field’s last used valueClass:

https://github.com/EsotericSoftware/kryo/blob/74db64334d9e16de95562ccd3a33dc7050dbc939/src/com/esotericsoftware/kryo/serializers/CompatibleFieldSerializer.java#L104

This saves 1 byte for non-final fields, but makes it impossible to re-use the same serializer for all instances of the field.

There are two options to resolve this:

  1. Add a flag to FieldSerializerConfig that toggles serializer caching and turn it off by default for CompatibleFieldSerializer and TaggedFieldSerializer
  2. Do not store the last known value class and rely on the default logic for CachedFields that only stores the value class for primitives, wrappers and final classes.

The first option is serialization compatible, but does not benefit from the performance gains from serializer caching. The second option likely breaks serialization compatibility with previous RCs and produces a slightly larger binary representation for non-final fields.

I’ll try to get input on this from @NathanSweet.