u/javaprof

▲ 29 r/Kotlin

Apache Fory was 10× slower than kotlinx.serialization

Most JSON benchmarks answer a useful but specific question: how fast is a library after initialization and JIT warmup?

I wanted to measure a different workload: a CLI, job, function, or short-lived process that performs only 1–100 JSON operations.

I added a fresh-JVM benchmark track to java-json-benchmark, alongside its existing warmed JMH suite.

The short version: there is no universal winner. In this experiment, kotlinx.serialization had the lowest cold cost, while Apache Fory delivered much higher warmed throughput.

Fresh-JVM results

Each sample launches a new JVM. These are median initialization-plus-first-operation times; complete process wall time is in parentheses.

Library Serialize Deserialize
kotlinx.serialization 13.37 ms (77.91 ms) 12.92 ms (77.05 ms)
Gson 39.54 ms (105.77 ms) 38.73 ms (104.72 ms)
Jackson 114.17 ms (178.83 ms) 134.03 ms (198.16 ms)
Apache Fory JSON 122.97 ms (857.80 ms) 125.39 ms (858.67 ms)

Fory's roughly 850 ms process lifetime is reproducible, while its measured initialization and operation phases take roughly 123–147 ms. The benchmark locates the extra time outside those phases, but does not yet explain it.

Warmed JMH results

For nominal 1000 KB payloads, aggregate throughput across 15 threads was:

Workload Fory kotlinx Ratio
Users serialization 22,304 ops/s 3,885 ops/s 5.74×
Users deserialization 8,228 ops/s 3,032 ops/s 2.71×
Clients serialization 21,356 ops/s 3,110 ops/s 6.87×
Clients deserialization 9,929 ops/s 1,204 ops/s 8.25×

Generated serializers therefore appear especially attractive for short-lived Kotlin applications, but Fory is considerably faster once the JVM is warm.

Methodology

Cold run:

  • 30 fresh JVMs per cell
  • 4 libraries × 2 operations × 3 batch sizes
  • 720 JVM processes total
  • batches of 1, 10, and 100 operations
  • seeded blocked randomization
  • common 2,142-byte Users-like Kotlin object graph
  • -Xms64m -Xmx64m -XX:+UseSerialGC

Warmed run:

  • JMH 1.35
  • 15 threads
  • 2 forks
  • one 5-second warmup iteration
  • three 1-second measurement iterations
  • nominal payloads from 1 to 1000 KB

Both ran on Liberica JDK 25.0.4, Debian 13/KVM, and an AMD Ryzen 7 7700X.

Important limitations

The cold track uses one common mutable @Serializable Kotlin DTO. Consequently, even Jackson, Gson, and Fory children include Kotlin stdlib and kotlinx-serialization-core. This is a common-application-model comparison, not minimal Java-only startup footprint.

It currently covers only a Users-like 2 KiB graph. It does not yet include the Clients model with UUID, BigDecimal, and date/time adapters. "Fresh JVM" also does not mean cold filesystem or artifact caches.

Everything published here:

No offense to Fory marketing, just for fun ;)

reddit.com
u/javaprof — 9 days ago