Why Replacing Fastjson with Gson Can Crash Your Service – A Deep Serialization Study
An in‑depth analysis reveals how replacing Fastjson with Gson caused a 400 MB payload and OOM in production, comparing serialization size, reference handling, and throughput across Gson, Fastjson, Java native serialization, and Hessian2, and offering recommendations for choosing the right framework.
Preface
JSON serialization frameworks have long been a security concern; fastjson has attracted many vulnerabilities recently, prompting security teams to demand upgrades. To avoid these issues, we replaced fastjson with Gson in a project, which led to an unexpected OOM problem in production.
Problem Description
A simple service serialized an object with fastjson and sent it via HTTP. After switching to Gson, the service generated a 400 MB request, which the HTTP client sent without size checks, causing the service to become unavailable.
Problem Analysis
Gson serializes each repeated object separately, while fastjson uses reference markers ($ref) for duplicate objects. In our data, many fields were duplicated, exposing Gson's inability to handle repeated objects efficiently.
Example code demonstrates the issue:
Foo foo = new Foo();
Bar bar = new Bar();
List<Foo> foos = new ArrayList<>();
for (int i = 0; i < 3; i++) {
foos.add(foo);
}
bar.setFoos(foos);
Gson gson = new Gson();
String gsonStr = gson.toJson(bar);
System.out.println(gsonStr);
String fastjsonStr = JSON.toJSONString(bar);
System.out.println(fastjsonStr);The output shows Gson repeats the object, while fastjson uses $ref.
Compression Ratio Test
Serialized object contains many attributes to simulate real business data.
Duplicate count: 200 identical references in a List.
Serialization methods: Gson, Fastjson, Java native, Hessian2.
Primary metric: compressed byte size; secondary: whether deserialized list preserves object identity.
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Foo foo = new Foo();
Bar bar = new Bar();
List<Foo> foos = new ArrayList<>();
for (int i = 0; i < 200; i++) {
foos.add(foo);
}
bar.setFoos(foos);
// gson
Gson gson = new Gson();
String gsonStr = gson.toJson(bar);
System.out.println(gsonStr.length());
Bar gsonBar = gson.fromJson(gsonStr, Bar.class);
System.out.println(gsonBar.getFoos().get(0) == gsonBar.getFoos().get(1));
// fastjson
String fastjsonStr = JSON.toJSONString(bar);
System.out.println(fastjsonStr.length());
Bar fastjsonBar = JSON.parseObject(fastjsonStr, Bar.class);
System.out.println(fastjsonBar.getFoos().get(0) == fastjsonBar.getFoos().get(1));
// java
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(bar);
oos.close();
System.out.println(baos.toByteArray().length);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
Bar javaBar = (Bar) ois.readObject();
ois.close();
System.out.println(javaBar.getFoos().get(0) == javaBar.getFoos().get(1));
// hessian2
ByteArrayOutputStream hessianBaos = new ByteArrayOutputStream();
Hessian2Output hOut = new Hessian2Output(hessianBaos);
hOut.writeObject(bar);
hOut.close();
System.out.println(hessianBaos.toByteArray().length);
ByteArrayInputStream hessianBais = new ByteArrayInputStream(hessianBaos.toByteArray());
Hessian2Input hIn = new Hessian2Input(hessianBais);
Bar hessianBar = (Bar) hIn.readObject();
hIn.close();
System.out.println(hessianBar.getFoos().get(0) == hessianBar.getFoos().get(1));
}
}Results:
gson:
62810
false
fastjson:
4503
true
Java:
1540
true
Hessian2:
686
trueConclusion: Fastjson’s reference handling dramatically reduces payload size; Hessian2 achieves the best compression, while Java native serialization also outperforms Gson.
Throughput Test
Benchmarking the four serializers shows fastjson achieving the highest throughput, followed by Gson, Hessian2, and Java.
Benchmark Mode Cnt Score Error Units
MicroBenchmark.fastjson thrpt 25 6724809.416 ± 1542197.448 ops/s
MicroBenchmark.gson thrpt 25 1508825.440 ± 194148.657 ops/s
MicroBenchmark.hessian2 thrpt 25 758643.567 ± 239754.709 ops/s
MicroBenchmark.java thrpt 25 734624.615 ± 66892.728 ops/sFastjson’s text‑based serialization delivers throughput an order of magnitude higher than binary alternatives.
Overall Test Conclusions
Fastjson’s $ref markers can be deserialized by Gson, but Gson cannot emit them.
Fastjson, Hessian2, and Java support circular references; Gson does not.
Fastjson can disable circular‑reference detection.
Gson loses object identity after a round‑trip, potentially inflating memory usage.
Hessian2 provides the best compression for large payloads.
Fastjson offers the highest throughput, suitable for high‑speed scenarios.
When choosing a serializer, consider reference handling, compression, throughput, and compatibility; Hessian2 and Fastjson are recommended for most cases.
Summary
Fastjson sacrifices safety for speed, leading to many vulnerabilities, but trade‑offs are inevitable. The best serializer depends on the specific problem; while Jackson may be superior in some aspects, the chosen library must meet the project's requirements.
Replacing a serialization framework should be done cautiously, ensuring the new library covers the features previously relied upon.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
