Search before asking
Version
- Writer: Fory Java
1.2.0
- Reader: Fory Java
1.3.0
- Java: Temurin JDK
21.0.5
- OS: macOS
The same sequence succeeds when both writer and reader use Fory 1.2.0.
Component(s)
Java
Minimal reproduce step
This repro uses one POJO with a field whose concrete type is a HashMap subclass. It depends only on fory-core.
Use this pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>repro</groupId>
<artifactId>fory-binary-compatibility-repro</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<fory.version>1.2.0</fory.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.fory</groupId>
<artifactId>fory-core</artifactId>
<version>${fory.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<mainClass>org.apache.fory.serializer.CompatibleSerializerTest</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
1. Define the model and runner
Save as src/main/java/org/apache/fory/serializer/CompatibleSerializerTest.java:
package org.apache.fory.serializer;
import java.util.HashMap;
import java.util.HexFormat;
import org.apache.fory.Fory;
import org.apache.fory.config.CompatibleMode;
import org.apache.fory.config.Language;
public final class CompatibleSerializerTest {
public static final class StringMap extends HashMap<String, String> {}
public static final class StringMapDocument {
public StringMap values;
}
public static void main(String[] args) {
Fory fory =
Fory.builder()
.requireClassRegistration(false)
.withCompatibleMode(CompatibleMode.COMPATIBLE)
.withLanguage(Language.JAVA)
.build();
if ("write".equals(args[0])) {
System.out.println(HexFormat.of().formatHex(fory.serialize(sample())));
return;
}
byte[] oldBytes = HexFormat.of().parseHex(args[1]);
StringMapDocument oldDocument = fory.deserialize(oldBytes, StringMapDocument.class);
System.out.println("read 1.2.0 payload: " + oldDocument.values.get("key"));
byte[] currentBytes = fory.serialize(oldDocument);
StringMapDocument currentDocument =
fory.deserialize(currentBytes, StringMapDocument.class);
System.out.println("next round trip: " + currentDocument.values.get("key"));
}
private static StringMapDocument sample() {
StringMapDocument document = new StringMapDocument();
document.values = new StringMap();
document.values.put("key", "value");
return document;
}
}
The package and enclosing class deliberately match the self-contained regression-test fixture, so the frozen payload can be consumed without a second repro-only source file.
2. Serialize with Fory 1.2.0 and dump the hex
HEX=$(mvn -q \
-Dfory.version=1.2.0 \
-Dexec.args=write \
compile exec:java | tail -n 1)
echo "$HEX"
The generated 200-byte payload is:
00ff1e003ec03a392b329f10300245ba26d01e011c9a2ba38d489140168c92207df44e63c1340564ec89140168c923d99253e76538a1a6eb00fe8dc2a308d98036540ba1240416151615ff20022e30f622b2e61109700045ba26d01e011c9a2ba38d489140168c922065744e63c1340564ec89140168c923d99253e76538a1a6eb00f00101042e30f622b2e61109700045ba26d01e011c9a2ba38d489140168c922065744e63c1340564ec89140168c923d99253e76538a1a6eb00f024010c6b65791476616c7565
3. Read the 1.2.0 payload with Fory 1.3.0
Use the same class and the hex from step 2:
mvn -q \
-Dfory.version=1.3.0 \
-Dexec.args="read $HEX" \
compile exec:java
The old payload itself is read successfully. The immediately following round-trip on the same Fory instance then fails.
4. Observe the regression
Fory 1.3.0 prints the successful old-payload read and then throws:
read 1.2.0 payload: value
[ERROR] ... Failed to deserialize input:
class org.apache.fory.serializer.CompatibleSerializerTest_StringMapForyCodecCompatible1_0
cannot be cast to class org.apache.fory.serializer.collection.MapLikeSerializer
As a control, run the same read sequence with Fory 1.2.0:
mvn -q \
-Dfory.version=1.2.0 \
-Dexec.args="read $HEX" \
compile exec:java
It succeeds:
read 1.2.0 payload: value
next round trip: value
What did you expect to see?
Fory 1.3.0 should continue to read compatible payloads written by Fory 1.2.0 without corrupting serializer metadata used by later operations on the same Fory instance.
Expected output:
read 1.2.0 payload: value
next round trip: value
What did you see instead?
After Fory 1.3.0 reads the Fory 1.2.0 payload, the next valid round-trip fails with a ClassCastException from a generated reader:
org.apache.fory.exception.DeserializationException: Failed to deserialize input
Caused by: java.lang.ClassCastException:
class org.apache.fory.serializer.CompatibleSerializerTest_StringMapForyCodecCompatible1_0
cannot be cast to class org.apache.fory.serializer.collection.MapLikeSerializer
This is a binary-compatibility regression: the sequence passes entirely on Fory 1.2.0 but fails when the reader is upgraded to Fory 1.3.0.
Anything Else?
The failure requires reusing the same Fory instance:
- Fory
1.3.0 reads the compatible payload produced by Fory 1.2.0.
- The same instance serializes another instance of the same model.
- Deserializing that new payload resolves
StringMap with a bean-compatible serializer where the generated reader requires a MapLikeSerializer.
Are you willing to submit a PR?
Independent verification
I reran this exact reproduction locally:
- Temurin JDK 21.0.5: Fory 1.2.0 succeeds; Fory 1.3.0 reads the old payload and then fails with the documented generated-codec-to-
MapLikeSerializer cast.
- The Fory 1.2.0 writer produced the documented 200-byte payload exactly (SHA-256:
d98499ddd80efe22ab0e2d488783cdd01b72fee676d20aad68dfdd89c2de11b6).
Search before asking
Version
1.2.01.3.021.0.5The same sequence succeeds when both writer and reader use Fory
1.2.0.Component(s)
Java
Minimal reproduce step
This repro uses one POJO with a field whose concrete type is a
HashMapsubclass. It depends only onfory-core.Use this
pom.xml:1. Define the model and runner
Save as
src/main/java/org/apache/fory/serializer/CompatibleSerializerTest.java:The package and enclosing class deliberately match the self-contained regression-test fixture, so the frozen payload can be consumed without a second repro-only source file.
2. Serialize with Fory 1.2.0 and dump the hex
The generated 200-byte payload is:
3. Read the 1.2.0 payload with Fory 1.3.0
Use the same class and the hex from step 2:
mvn -q \ -Dfory.version=1.3.0 \ -Dexec.args="read $HEX" \ compile exec:javaThe old payload itself is read successfully. The immediately following round-trip on the same
Foryinstance then fails.4. Observe the regression
Fory
1.3.0prints the successful old-payload read and then throws:As a control, run the same read sequence with Fory
1.2.0:mvn -q \ -Dfory.version=1.2.0 \ -Dexec.args="read $HEX" \ compile exec:javaIt succeeds:
What did you expect to see?
Fory
1.3.0should continue to read compatible payloads written by Fory1.2.0without corrupting serializer metadata used by later operations on the sameForyinstance.Expected output:
What did you see instead?
After Fory
1.3.0reads the Fory1.2.0payload, the next valid round-trip fails with aClassCastExceptionfrom a generated reader:This is a binary-compatibility regression: the sequence passes entirely on Fory
1.2.0but fails when the reader is upgraded to Fory1.3.0.Anything Else?
The failure requires reusing the same
Foryinstance:1.3.0reads the compatible payload produced by Fory1.2.0.StringMapwith a bean-compatible serializer where the generated reader requires aMapLikeSerializer.Are you willing to submit a PR?
Independent verification
I reran this exact reproduction locally:
MapLikeSerializercast.d98499ddd80efe22ab0e2d488783cdd01b72fee676d20aad68dfdd89c2de11b6).