Search before asking
Version
Fory 1.6.1 and 1.7.0 (latest). Fails on Java 8, passes on Java 21.
fory-core declares maven.compiler.source/target = 8, so Java 8 is a supported target.
Failing JDK : 1.8.0_491-b10, HotSpot 64-Bit Server VM 25.491-b10
Passing JDK : Temurin 21.0.11+10-LTS
OS : macOS 15 (Darwin 24.6.0), arm64
Component(s)
Java
Minimal reproduce step
fory-multi-instance-repro.zip
On Java 8, constructing a second Fory instance in the same JVM fails, once an earlier
instance has serialized an object graph large enough to trigger JIT codegen. The failure comes
from ClassResolver.initialize() while registering Fory's own built-in types — the string
it fails to encode is the package or type name of a Fory internal class, which is ASCII by
construction.
A single Fory instance never fails, at any payload size (checked to ~3.3 MB / 150k objects),
so this is not a serialization-size problem. Once it throws, every later Fory construction in
that JVM fails the same way.
The practical impact is on any application holding more than one Fory — two library modules
that each keep a static instance, or one instance per classloader in a container. The second
can become permanently un-constructible depending on what the first happened to serialize.
The attached fory-multi-instance-repro.zip is a single self-contained file, no dependencies
beyond fory-core:
unzip fory-multi-instance-repro.zip && cd fory-multi-instance-repro
curl -sO https://repo1.maven.org/maven2/org/apache/fory/fory-core/1.7.0/fory-core-1.7.0.jar
javac -cp fory-core-1.7.0.jar ForyMultiInstanceRepro.java
java -cp fory-core-1.7.0.jar:. ForyMultiInstanceRepro
Optional args: [items] [instances] [builderConfig], defaults 800 4 copy+num.
Some configurations are probabilistic, so run it a few times to see a rate:
for i in $(seq 1 10); do java -cp fory-core-1.7.0.jar:. ForyMultiInstanceRepro; done
What did you expect to see?
OK items=800 instances=4 bytes=194341
What did you see instead?
10 of 10 runs fail on Java 8; 0 of 10 on Java 21.
FAIL at instance 2 of 4 (items=800, bytes=194341): java.lang.IllegalArgumentException: Non-ASCII characters in meta string are not allowed
java.lang.IllegalArgumentException: Non-ASCII characters in meta string are not allowed
at org.apache.fory.meta.MetaStringEncoder.encodeBinary(MetaStringEncoder.java:92)
at org.apache.fory.resolver.SharedRegistry.getEncodedMetaString(SharedRegistry.java:337)
at org.apache.fory.resolver.SharedRegistry.getPackageEncodedMetaString(SharedRegistry.java:309)
at org.apache.fory.resolver.TypeInfo.<init>(TypeInfo.java:106)
at org.apache.fory.resolver.ClassResolver.registerInternalImpl(ClassResolver.java:728)
at org.apache.fory.resolver.ClassResolver.registerInternal(ClassResolver.java:716)
at org.apache.fory.resolver.ClassResolver.initialize(ClassResolver.java:273)
at org.apache.fory.Fory.<init>(Fory.java:155)
at org.apache.fory.config.ForyBuilder.newFory(ForyBuilder.java:789)
at org.apache.fory.ThreadLocalFory.newFory(ThreadLocalFory.java:67)
Variations across runs of the same command, all with the above root cause:
- it also fails via getTypeNameEncodedMetaString (SharedRegistry.java:317), so either meta
string can be the one that fails;
- on 1.7.0 it is sometimes surfaced wrapped, as
org.apache.fory.exception.SerializationException: java.lang.RuntimeException: Create sequential serializer failed
(7/10 raw, 3/10 wrapped in one sample of 10);
- the failing instance index is usually 2, occasionally 3.
Results, 10 runs per cell:
fory items instances Java 8 Java 21
1.6.1 800 4 10/10 fail 0/10
1.7.0 800 4 10/10 fail 0/10
1.6.1 800 1 0/10 0/10
Anything Else?
Conditions, each verified by removing it:
- Java 8. Not reproducible on Java 21, including at 8 instances and 4x the payload.
- More than one Fory instance constructed in the JVM.
- An earlier serialization large enough to trigger JIT codegen. Built-in types alone never
trigger it. A flat HashMap<Integer, UserType> of 6000 entries does NOT reproduce; several
distinct generic shapes (Map<Integer,.>, Map<Long,.>, Set) do. It is not a simple
size threshold — the same shape reproduces at 2000 entries per field but not at 10000.
- withRefTracking(true).
The failure rate tracks how many builder methods are called. With withLanguage +
requireClassRegistration(false) + withRefTracking(true) alone it reproduces intermittently;
adding further builder calls raises the rate (8 runs each, Java 8; reproduce each row with the
third arg, e.g. ... ForyMultiInstanceRepro 800 4 min):
builder config arg fail rate
minimal (above) min 2/8
- withRefCopy(true) copy 2/8
- serializeEnumByName(true) enum 6/8
- withNumberCompressed(false) num 7/8
- withRefCopy(true) + withNumberCompressed(false) copy+num 8/8
serializeEnumByName(true) raises the rate although the payload contains no enums, and
withRefCopy(true) affects only copy(), which is never called here. That suggests the trigger is
sensitive to the number of action-recording builder methods replayed in ForyBuilder.factory(),
rather than to any single option's semantics.
withCodegen(false) and withAsyncCompilation(true) each reduce the rate without eliminating it,
so neither is a workaround.
Ruled out:
- A shared SharedRegistry instance. Fory.(builder, classLoader, sharedRegistry) does
new SharedRegistry() when the argument is null, so separate instances do not share one.
- The isLatin/ASCII range gap. StringEncodingUtils.isLatin(char[]) accepts chars <= 255 while
the LOWER_SPECIAL encoders reject > 127, but encodeBinary(String, Encoding[]) correctly
falls back to UTF-8 — verified with "café", "aÿ", "aĀ", "a中" on both JDKs.
The gap applies only to the forced-encoding overload, which is what this path uses.
- The multi-release jar. PlatformStringUtils and MemoryBuffer are overridden only under
META-INF/versions/25/, so Java 8 and Java 21 both load the base classes.
Possibly related:
Suggested hardening, independent of root cause:
MetaStringEncoder.encodeBinary(String, Encoding) throws when the forced encoding cannot
represent the string, while the Encoding[] overload falls back to UTF-8. Making the
forced-encoding path fall back the same way would downgrade this from an error that permanently
prevents Fory construction in the JVM to correct (if slightly larger) output.
Are you willing to submit a PR?
Search before asking
Version
Component(s)
Java
Minimal reproduce step
fory-multi-instance-repro.zip
On Java 8, constructing a second
Foryinstance in the same JVM fails, once an earlierinstance has serialized an object graph large enough to trigger JIT codegen. The failure comes
from
ClassResolver.initialize()while registering Fory's own built-in types — the stringit fails to encode is the package or type name of a Fory internal class, which is ASCII by
construction.
A single
Foryinstance never fails, at any payload size (checked to ~3.3 MB / 150k objects),so this is not a serialization-size problem. Once it throws, every later
Foryconstruction inthat JVM fails the same way.
The practical impact is on any application holding more than one
Fory— two library modulesthat each keep a
staticinstance, or one instance per classloader in a container. The secondcan become permanently un-constructible depending on what the first happened to serialize.
The attached
fory-multi-instance-repro.zipis a single self-contained file, no dependenciesbeyond
fory-core:Optional args:
[items] [instances] [builderConfig], defaults800 4 copy+num.Some configurations are probabilistic, so run it a few times to see a rate:
What did you expect to see?
OK items=800 instances=4 bytes=194341
What did you see instead?
Anything Else?
Conditions, each verified by removing it:
trigger it. A flat HashMap<Integer, UserType> of 6000 entries does NOT reproduce; several
distinct generic shapes (Map<Integer,.>, Map<Long,.>, Set) do. It is not a simple
size threshold — the same shape reproduces at 2000 entries per field but not at 10000.
The failure rate tracks how many builder methods are called. With withLanguage +
requireClassRegistration(false) + withRefTracking(true) alone it reproduces intermittently;
adding further builder calls raises the rate (8 runs each, Java 8; reproduce each row with the
third arg, e.g.
... ForyMultiInstanceRepro 800 4 min):builder config arg fail rate
minimal (above) min 2/8
serializeEnumByName(true) raises the rate although the payload contains no enums, and
withRefCopy(true) affects only copy(), which is never called here. That suggests the trigger is
sensitive to the number of action-recording builder methods replayed in ForyBuilder.factory(),
rather than to any single option's semantics.
withCodegen(false) and withAsyncCompilation(true) each reduce the rate without eliminating it,
so neither is a workaround.
Ruled out:
new SharedRegistry() when the argument is null, so separate instances do not share one.
the LOWER_SPECIAL encoders reject > 127, but encodeBinary(String, Encoding[]) correctly
falls back to UTF-8 — verified with "café", "aÿ", "aĀ", "a中" on both JDKs.
The gap applies only to the forced-encoding overload, which is what this path uses.
META-INF/versions/25/, so Java 8 and Java 21 both load the base classes.
Possibly related:
cross-instance coupling).
Suggested hardening, independent of root cause:
MetaStringEncoder.encodeBinary(String, Encoding) throws when the forced encoding cannot
represent the string, while the Encoding[] overload falls back to UTF-8. Making the
forced-encoding path fall back the same way would downgrade this from an error that permanently
prevents Fory construction in the JVM to correct (if slightly larger) output.
Are you willing to submit a PR?