[Java] Multiple Fory instances cannot be constructed on Java 8 (Non-ASCII characters in meta string)
- Dominant language
- Java
- Stars
- 4.5k
- Forks
- 443
- Avg merge
- 5h 59m
- Merged PRs (30d)
- 77
Description
### Search before asking
- [x] I had searched in the [issues](https://github.com/apache/fory/issues) and found no similar issues.
### 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](https://github.com/user-attachments/files/31758009/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.(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.(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:
1. Java 8. Not reproducible on Java 21, including at 8 instances and 4x the payload.
2. More than one Fory instance constructed in the JVM.
3. An earlier serialization large enough to trigger JIT codegen. Built-in types alone never
trigger it. A flat HashMap of 6000 entries does NOT reproduce; several
distinct generic shapes (Map, Map, Set) do. It is not a simple
size threshold — the same shape reproduces at 2000 entries per field but not at 10000.
4. 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:
- #3828 — deserialization depending on Fory instance state (different symptom, same theme of
cross-instance coupling).
- #3546 — introduced caching of serializers and TypeInfo in SharedRegistry.
- #2070 — a prior Java 8 vs 8+ divergence in the same internal registration path.
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?
- [ ] I'm willing to submit a PR!
Contributor guide
Research direction
Run the attached ForyMultiInstanceRepro on Java 8 and Java 21, then inspect MetaStringEncoder.encodeBinary and the SharedRegistry calls at lines 309, 317, and 337. Trace how ClassResolver.initialize registers built-in types across multiple Fory instances; done means repeated construction succeeds on Java 8 without the Non-ASCII meta-string failure while preserving the existing encoding behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100