TL;DR: Apache Fory JSON is a high-performance serialization framework that maps Java objects to and from standard JSON text and UTF-8 bytes. It supports common Java models and runs on JDK 8+, Android, and GraalVM Native Image. Fory JSON is the fastest Java JSON serialization framework in the published benchmarks: up to 10.91× the throughput of Jackson and 10.89× the throughput of Gson with 1000 KB payloads, and up to 5.55× the throughput of Jackson and 10.00× the throughput of Gson in the jvm-serializers MediaContent benchmark.
- GitHub: apache/fory
- Documentation: Fory JSON
- Wider 1000 KB benchmark context: java-json-benchmark
Why JSON Performance Matters
JSON sits on many Java service hot paths: HTTP APIs, browser traffic, event payloads, logs, configuration, and integrations with systems that do not share a binary protocol. Parsing and serialization costs recur across requests, consuming CPU and creating temporary objects.
Fory already provides compact binary object serialization and a cross-language protocol. Fory JSON serves a different use case by mapping Java objects to standard JSON text and UTF-8 bytes. The output remains readable by browsers, command-line tools, and any standards-compliant JSON implementation.
Quick Start
Fory JSON 1.6.0 is available from Maven Central:
<dependency>
<groupId>org.apache.fory</groupId>
<artifactId>fory-json</artifactId>
<version>1.6.0</version>
</dependency>
Create one ForyJson instance and reuse it. The built instance is immutable and thread-safe.
import org.apache.fory.json.ForyJson;
public final class JsonExample {
private static final ForyJson JSON = ForyJson.builder().build();
public static final class User {
public long id;
public String name;
public User() {}
User(long id, String name) {
this.id = id;
this.name = name;
}
}
public static void main(String[] args) {
User input = new User(7, "Alice");
String text = JSON.toJson(input);
byte[] utf8 = JSON.toJsonBytes(input);
User fromText = JSON.fromJson(text, User.class);
User fromUtf8 = JSON.fromJson(utf8, User.class);
System.out.println(text); // {"id":7,"name":"Alice"}
System.out.println(fromText.name); // Alice
System.out.println(fromUtf8.name); // Alice
}
}
Fory JSON directly supports both String and byte-array APIs. For generic roots, a TypeRef<List<User>> preserves the element type during deserialization and declared-type serialization. Fory JSON can also write a complete UTF-8 document to an OutputStream without taking ownership of the stream.
How Fory JSON Achieves High Performance
Fory JSON's performance comes primarily from four implementation choices.
Minimal temporary allocation. A ForyJson instance reuses prepared type metadata, execution state, and retained writer buffers. For most basic scalar values, the hot serialization path is effectively zero-allocation beyond the requested output: Fory writes the value directly into its output buffer instead of first converting it into a Java String. Returning a String or byte[] still creates that result, and buffer growth or a cold fallback can allocate.
Highly optimized primitive writes. Integers and longs use direct digit encoders, while floating-point values use direct formatting paths where the JDK provides them. Booleans, numbers, and quoted strings go straight to the active String or UTF-8 writer. There is no per-value String.valueOf(...) round trip on the hot path.
Bulk memory operations. Common Latin-1 and ASCII text is scanned and copied in 8-byte or 16-byte chunks. Generated writers can also use packed property prefixes and object framing, reducing per-character branches and repeated writes.
Runtime code generation. Fory JSON prepares a codec for each Java type it encounters. On a standard JDK, code generation and asynchronous compilation are enabled by default. Generated codecs specialize field access, property names, framing, and primitive operations for the target class instead of rediscovering the same property model on every call. An interpreted path remains available for restricted environments and diagnostics.
The public API preserves those fast paths. Applications that need UTF-8 output can call toJsonBytes and fromJson(byte[], type) directly, while text-oriented code can use the String APIs. Custom codecs also stream through Fory's reader and writer instead of building an intermediate JSON tree.
Performance Benchmarks
The performance results cover two workloads: a large-payload java-json-benchmark run and the smaller jvm-serializers MediaContent benchmark. Both report throughput in operations per second, so higher is better. Each subsection describes its configuration.
The tables compare Fory JSON only with Jackson and Gson. For the wider large-payload matrix, exact payload setup, and the benchmark integration, see java-json-benchmark.
java-json-benchmark: 1000 KB payloads
The 1000 KB suite measures workloads in which each invocation performs substantial parsing, object traversal, and output work.
The 1000 KB run used Fory JSON 1.6.0, Jackson Databind 2.17.1, and Gson 2.11.0 with the databind API. JMH ran two forks and three threads. Each fork used five 3-second warmup iterations and five 3-second measurement iterations. The Users and Clients payloads each contained one 1000 KB object per invocation.


| Payload | Operation | Fory JSON ops/s | Jackson ops/s | Gson ops/s | vs. Jackson | vs. Gson |
|---|---|---|---|---|---|---|
| Users | Serialization | 11,867.566 ± 136.846 | 2,225.296 ± 687.106 | 1,674.083 ± 12.558 | 5.33× | 7.09× |
| Users | Deserialization | 6,872.876 ± 46.998 | 1,940.172 ± 58.242 | 1,217.513 ± 12.358 | 3.54× | 5.65× |
| Clients | Serialization | 11,895.269 ± 183.251 | 1,706.314 ± 36.263 | 1,298.288 ± 27.405 | 6.97× | 9.16× |
| Clients | Deserialization | 6,442.262 ± 627.116 | 590.656 ± 10.849 | 591.350 ± 6.444 | 10.91× | 10.89× |
Across these four large-payload cases, Fory JSON delivers 3.54× to 10.91× the throughput of Jackson and 5.65× to 10.89× that of Gson.
jvm-serializers MediaContent benchmark
The second benchmark uses the jvm-serializers MediaContent model, which contains a media object and a list of images. This benchmark covers much smaller objects than the 1000 KB suite and separates Java String APIs from UTF-8 byte-array APIs.
The jvm-serializers benchmark ran on an Apple M4 Pro with JDK 26.0.1. It used one JMH fork and one thread, with three 2-second warmup iterations followed by five 2-second measurement iterations.


| Representation | Operation | Fory JSON ops/s | Jackson ops/s | Gson ops/s | vs. Jackson | vs. Gson |
|---|---|---|---|---|---|---|
| String | Serialize | 7,387,465 | 2,049,368 | 1,084,042 | 3.60× | 6.81× |
| String | Deserialize | 2,897,955 | 1,074,885 | 902,772 | 2.70× | 3.21× |
| UTF-8 bytes | Serialize | 10,375,498 | 1,868,614 | 1,037,211 | 5.55× | 10.00× |
| UTF-8 bytes | Deserialize | 3,077,158 | 1,268,397 | 933,079 | 2.43× | 3.30× |
Fory JSON records the highest throughput in all four operations. Its largest advantage appears on UTF-8 serialization, where it exceeds 10 million operations per second and reaches 5.55× the throughput of Jackson and 10.00× that of Gson.
The String and UTF-8 groups are deliberately separate. The String group excludes UTF-8 conversion. The byte group uses direct byte-array APIs where a library provides them; Gson includes its required String-to-byte and byte-to-String conversion.
Results vary by workload, but Fory JSON is the fastest framework in both benchmark configurations shown here.
Java Object Mapping
Fory JSON maps existing Java application models directly, without requiring hand-written transfer objects:
- ordinary mutable classes and inherited fields;
- Java records and immutable classes built through
JsonCreator; - generic collections and maps through
TypeRef; - Java time, optionals, atomics, UUIDs, paths, big numbers, enums, and common collection types;
JsonObjectandJsonArraytree models for dynamic targets.
Property discovery can combine fields with JavaBean getters and setters, or switch to field-only mode. A finite JsonSubTypes table handles declared polymorphic models without accepting arbitrary class names from input.
Annotation-Based Object Mapping
Fory JSON provides its own annotations in org.apache.fory.json.annotation. The annotation model will feel familiar to Jackson users, covering property names and ordering, creators, formatting, polymorphism, and validation. These are independent Fory JSON APIs, not Jackson-compatible annotations.
Additional annotations support ignoring properties only during serialization or deserialization, Base64 byte arrays, raw or complete value representations, flattened objects, and dynamic members. See the Fory JSON annotations guide for the complete reference.
The following model combines several annotations: it renames a fixed property, formats a date, flattens an owner, captures dynamic members, and validates the completed object after reading:
import java.time.LocalDate;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.fory.json.annotation.JsonAnyProperty;
import org.apache.fory.json.annotation.JsonFormat;
import org.apache.fory.json.annotation.JsonProperty;
import org.apache.fory.json.annotation.JsonUnwrapped;
import org.apache.fory.json.annotation.JsonValidator;
public final class Event {
@JsonProperty("event_id")
public long id;
@JsonFormat(pattern = "dd/MM/uuuu")
public LocalDate day;
@JsonUnwrapped(prefix = "owner_")
public Owner owner;
@JsonAnyProperty
public Map<String, Object> attributes = new LinkedHashMap<>();
@JsonValidator
public void validate() {
if (id <= 0) {
throw new IllegalArgumentException("event_id must be positive");
}
}
public static final class Owner {
public String name;
}
}
JsonCreator supports immutable classes, while JsonPropertyOrder makes output order explicit. JsonValue, JsonRawValue, and JsonBase64 handle specialized value shapes without changing the rest of the object mapping.
For a third-party type that cannot carry annotations, a Mixin overlays the same Fory JSON annotations without changing or wrapping the target class:
import org.apache.fory.json.ForyJson;
import org.apache.fory.json.annotation.JsonMixin;
import org.apache.fory.json.annotation.JsonProperty;
@JsonMixin(target = ThirdPartyUser.class)
abstract class ThirdPartyUserMixin {
@JsonProperty("user_id")
long id;
}
ForyJson json =
ForyJson.builder()
.registerMixin(ThirdPartyUserMixin.class)
.build();
When built-in mapping and annotations are not enough, JsonValueCodec<T> can handle one complete JSON value and stream it through Fory's reader and writer. Child codec selections can customize collection elements, optional contents, and map keys or values without replacing the surrounding container mapping. See the custom codecs guide for details.
Closed-world polymorphism with JsonSubTypes
JsonSubTypes maps a declared base type to a complete set of permitted implementations and logical names. The discriminator in JSON selects from this table; it never supplies a Java class name.
import org.apache.fory.json.ForyJson;
import org.apache.fory.json.annotation.JsonSubTypes;
public final class PaymentExample {
@JsonSubTypes(
property = "kind",
value = {
@JsonSubTypes.Type(value = CardPayment.class, name = "card"),
@JsonSubTypes.Type(value = BankTransfer.class, name = "bank_transfer")
})
public interface Payment {}
public static final class CardPayment implements Payment {
public String lastFour;
}
public static final class BankTransfer implements Payment {
public String iban;
}
public static void main(String[] args) {
ForyJson json = ForyJson.builder().build();
CardPayment card = new CardPayment();
card.lastFour = "4242";
String text = json.toJson(card, Payment.class);
Payment copy = json.fromJson(text, Payment.class);
System.out.println(text); // {"kind":"card","lastFour":"4242"}
System.out.println(copy.getClass()); // class PaymentExample$CardPayment
}
}
Serializing with the declared Payment type activates its subtype table. During reading, kind must match card or bank_transfer; an unknown name is rejected. For containers, a TypeRef<List<Payment>> carries the same declared base type for every element. The annotations guide covers the alternative wrapper representations and complete validation rules.
JDK, Android, and GraalVM Native Image Support
The same fory-json artifact supports Java 8 and later. Java records require Java 17 or later.
On Android API level 26+, Fory JSON automatically uses interpreted object mapping because runtime compilation is unavailable. The Fory annotation processor can generate direct model access and exact R8 rules for JsonType classes and Mixins.
GraalVM Native Image has its own build-time integration. Mark reachable models with JsonType so the build can prepare their access metadata. To use generated codecs for a specific ForyJson configuration in the native executable, expose that completed configuration from a reachable ForyJsonProvider. Other prepared configurations continue to use interpreted codecs.
Applications can therefore use the same mapping model across the JVM, Android, and native executables, with execution adapted to each runtime's code-generation capabilities.
Security controls for untrusted JSON
A fast parser still needs a strict type boundary. Fory JSON deserializes into the type declared by the application; JSON input cannot select an arbitrary Java class. Polymorphism is closed-world: a JsonSubTypes declaration defines the complete finite set of permitted subtypes, and input can select only a logical name from that table. Fory JSON also applies a fixed type disallow list and provides JsonTypeChecker for application-defined allow-lists. Together, these controls prevent untrusted JSON from materializing arbitrary classes through open-world polymorphic deserialization.
Input depth defaults to 20. A separate graph-memory budget defaults to 128 MiB per root read and estimates the retained object graph created by arrays, collections, maps, records, and application objects. JsonValidator methods can enforce domain rules after a mapped object is complete.
Those controls do not replace HTTP body limits, authentication, authorization, timeouts, or endpoint-specific validation. They give the JSON layer clear boundaries to combine with those external controls. The Fory JSON security guide documents the accounting model and recommended negative tests.
Choosing Between Fory JSON and Binary Serialization
Choose Fory JSON when an application must exchange standard JSON: public APIs, browser clients, configuration, logs, or existing JSON integrations. This preserves format interoperability while using a Java implementation designed around generated codecs and reusable state.
Choose Fory's binary object serialization when both sides can use a binary protocol and the application needs features JSON does not carry, such as cross-language schema metadata, shared-reference identity, or circular object graphs. The two formats solve different problems and can coexist within the same service.
Learn More
To evaluate Fory JSON, replace one representative Jackson or Gson round trip, reuse a single ForyJson instance, and benchmark the application's actual model and JDK settings. The published numbers show the available headroom; the application workload determines how much of it applies.
- Read the Fory JSON overview.
- Run the Getting Started example.
- Inspect the complete
jvm-serializersMediaContent benchmark. - Review the 1000 KB benchmark and broader matrix.
- Join development at apache/fory.
Fory JSON preserves standard JSON interoperability while providing a high-performance implementation for Java services.
