Apache Fory with Spring Boot: 3x JSON Serialization Performance Boost

This article introduces Apache Fory, a high-performance multi-language serialization framework, and demonstrates its integration with Spring Boot 3.5.0, showing up to 3x throughput improvement over Jackson in JSON benchmarks with code examples for binary, JSON, and row-format serialization.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Apache Fory with Spring Boot: 3x JSON Serialization Performance Boost

Introduction to Apache Fory

Apache Fory is a high-speed multi-language serialization framework designed for cross-language, cross-platform compact and high-throughput serialization. It handles application objects directly, supports shared schemas for stable contracts, and preserves object-graph features such as shared references, circular references, and polymorphic value types. Supported languages include Java, Python, C++, Go, Rust, JavaScript/TypeScript, C#, Swift, Dart, Scala, and Kotlin. This article focuses on Java usage with Spring Boot 3.5.0.

Quick Start

Add the core dependency:

<dependency>
  <groupId>org.apache.fory</groupId>
  <artifactId>fory-core</artifactId>
  <version>1.7.3</version>
</dependency>

Basic serialization example:

public final class ForyTest {
  public static final class User {
    public long id;
    public String name;
    public User() {}
    public User(long id, String name) {
      this.id = id;
      this.name = name;
    }
  }
  public static void main(String[] args) {
    Fory fory = Fory.builder().withXlang(true).build();
    fory.register(User.class, 1);
    byte[] bytes = fory.serialize(new User(1, "Pack_xg"));
    User decoded = (User) fory.deserialize(bytes);
    System.out.println(decoded.name);
  }
}

Important: Reuse a single Fory instance per thread; do not create a new instance for each value. Fory is not thread-safe; for concurrent access use ThreadSafeFory.

Fory JSON

For JSON-only needs, add fory-json instead of fory-core:

<dependency>
  <groupId>org.apache.fory</groupId>
  <artifactId>fory-json</artifactId>
  <version>1.7.3</version>
</dependency>

Usage example:

ForyJson json = ForyJson.builder().build();
String text = json.toJson(new User(1, "Pack_xg"));
System.err.println(text);
User jsonDecoded = json.fromJson(text, User.class);
System.out.println(jsonDecoded.name);

Output:

{"id":1,"name":"Pack_xg"}
Pack_xg

Thread-Safe Usage

For multi-threaded environments, use ThreadSafeFory:

User user = new User(1, "Pack_xg");
ThreadSafeFory fory = Fory.builder()
  .withXlang(true)
  .buildThreadSafeFory();
fory.register(User.class, 1);
byte[] bytes = fory.serialize(user);
System.out.println(fory.deserialize(bytes));

Recommended pattern: create a static final ThreadSafeFory instance and register types in a static initializer.

Row Format (Random Access)

Fory provides a row format enabling random access to nested fields without full deserialization, reducing overhead when only partial data is needed. Features include zero-copy access, partial deserialization, skip serialization, cross-language compatibility (Python, Java, C++, Rust), and automatic conversion to Apache Arrow columnar format.

Dependency:

<dependency>
  <groupId>org.apache.fory</groupId>
  <artifactId>fory-format</artifactId>
  <version>1.7.3</version>
</dependency>

Example demonstrating zero-copy access to a large object graph:

public static class Bar {
  public String f1;
  public List<Long> f2;
}
public static class Foo {
  public int f1;
  public List<Integer> f2;
  public Map<String, Integer> f3;
  public List<Bar> f4;
}
public static void main(String[] args) {
  RowEncoder<Foo> encoder = Encoders.bean(Foo.class);
  Foo foo = new Foo();
  foo.f1 = 10;
  foo.f2 = IntStream.range(0, 1_000_000).boxed().collect(Collectors.toList());
  foo.f3 = IntStream.range(0, 1_000_000).boxed().collect(Collectors.toMap(i -> "k" + i, i -> i));
  List<Bar> bars = new ArrayList<>(1_000_000);
  for (int i = 0; i < 1_000_000; i++) {
    Bar bar = new Bar();
    bar.f1 = "s" + i;
    bar.f2 = LongStream.range(0, 10).boxed().collect(Collectors.toList());
    bars.add(bar);
  }
  foo.f4 = bars;
  BinaryRow binaryRow = encoder.toRow(foo);
  Foo decoded = encoder.fromRow(binaryRow);
  BinaryArray f2Array = binaryRow.getArray(1);
  System.err.println(f2Array.getInt32(5));
  System.err.println(binaryRow.getInt32(0));
  BinaryArray f4Array = binaryRow.getArray(3);
  BinaryRow bar10 = f4Array.getStruct(10);
  long value = bar10.getArray(1).getInt64(5);
  System.err.println(value);
}

Performance Benchmarks

Benchmarks compare fory-json, Jackson, and Gson using identical data. Two groups: String (no UTF-8 conversion) and UTF-8 bytes (direct byte-array APIs where available). Gson includes String-to-UTF-8 encoding and UTF-8-to-String decoding.

Results (referenced charts in article):

String group: Fory JSON shows significantly higher throughput than Jackson and Gson.

UTF-8 bytes group: Fory JSON maintains a clear lead, achieving roughly 3x the throughput of Jackson.

The article includes bar charts for both groups and a final summary chart illustrating the performance gap.

Spring Boot Integration

Add the Spring Boot 3 starter:

<dependency>
  <groupId>io.github.chaokunyang</groupId>
  <artifactId>fory-json-spring-boot3-starter</artifactId>
  <version>1.1.0</version>
</dependency>

After adding this dependency, Spring MVC uses ForyJsonHttpMessageConverter for request/response JSON handling. Controllers continue to use standard @RequestBody, return types, Mono, and Flux APIs.

Data model with Fory JSON annotations:

import org.apache.fory.json.annotation.JsonProperty;
public class User {
  private Long id;
  @JsonProperty("u_name")
  private String name;
  @JsonProperty("u_age")
  private Integer age;
  @JsonProperty("u_email")
  private String email;
  @JsonProperty("u_address")
  private String address;
}

Controller example:

@PostMapping(
  consumes = MediaType.APPLICATION_JSON_VALUE,
  produces = MediaType.APPLICATION_JSON_VALUE)
public List<User> echoBatch(@RequestBody List<User> users) {
  return users;
}

JMeter tests comparing Jackson and Fory converters show Fory achieving higher throughput and lower latency.

Custom Mapping Configuration

Define a ForyJson bean to customize behavior. Example: write long values as strings (to avoid JavaScript precision loss) and omit empty properties:

@Configuration(proxyBeanMethods = false)
public class JsonConfiguration {
  @Bean
  public ForyJson foryJson(ApplicationContext applicationContext,
                           ObjectProvider<ForyJsonModule> modules) {
    ForyJsonBuilder builder = ForyJson.builder()
      .withClassLoader(applicationContext.getClassLoader())
      .writeLongAsString(true)
      .defaultPropertyInclusion(Include.NON_EMPTY);
    modules.orderedStream().forEach(builder::withModule);
    return builder.build();
  }
}
writeLongAsString(true)

serializes long identifiers as strings, preserving exact values for JavaScript clients. Include.NON_EMPTY omits null, empty strings, empty collections, empty maps, and empty Optionals, while retaining numeric zero and boolean false. To output null fields, use builder.writeNullFields(true). Per-field override with @JsonProperty(include = Include.ALWAYS).

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaperformance benchmarkspring-bootSpring MVCrow formatJSON serializationApache Foryserialization framework
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.