JMH-Based Performance Benchmarking of Java JSON Libraries: Gson, Fastjson, Jackson, and json-lib
Performance-sensitive systems often move JSON back and forth at high volume. When latency or throughput matters, the choice of JSON library can influence CPU time and allocation rates. Using JMH, the runtime characteristics of four popular Java libraries—Gson, Fastjson, Jackson, and json-lib—are contrasted for both serialization and deserialization workloads.
Selection considerations
- Converting JSON strings to tree models or POJOs
- Converting POJOs to compact JSON strings
- Handling nested structures and collections
- API ergonomics and configurability
- Dependency footprint and compatibility
Libraries overview
-
Gson (https://github.com/google/gson)
- Mature, zero external dependencies, straightforward API via Gson#toJson/fromJson.
- Works well for typical POJO bindings; advanced configuraton available (TypeAdapters, ExclusionStrategies, etc.).
-
Fastjson (https://github.com/alibaba/fastjson)
- Performance-focused implementation with minimal dependencies.
- Very fast for many scenarios; care is advised for complex generic types and configuration nuances.
-
Jackson (https://github.com/FasterXML/jackson)
- Widely adopted; provides streaming (jackson-core), data-binding (jackson-databind), and annotations (jackson-annotations).
- Strong extensibility, good performance, and robust feature set (modules for Java 8 types, Afterburner, etc.).
-
json-lib (http://json-lib.sourceforge.net/index.html)
- Older library with multiple transitive dependencies.
- Limited in handling of nested generics and modern Java types; not performance-oriented.
Maven coordinates
Use versions appropriate for the target runtime; the following block illustrates the four libraries as standalone dependencies.
<!-- JSON libraries -->
<dependencies>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.46</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.4</version>
</dependency>
</dependencies>
Utility wrappers
FastjsonHelper.java
import com.alibaba.fastjson.JSON;
public final class FastjsonHelper {
private FastjsonHelper() {}
public static String toJson(Object value) {
return JSON.toJSONString(value);
}
public static <T> T fromJson(String payload, Class<T> type) {
return JSON.parseObject(payload, type);
}
}
GsonHelper.java
import com.google.gson.*;
public final class GsonHelper {
private static final Gson G = new GsonBuilder().create();
private GsonHelper() {}
public static String toJson(Object value) {
return G.toJson(value);
}
public static <T> T fromJson(String payload, Class<T> type) {
return G.fromJson(payload, type);
}
public static String pretty(String compactJson) {
JsonElement tree = JsonParser.parseString(compactJson);
return new GsonBuilder().setPrettyPrinting().create().toJson(tree);
}
}
JacksonHelper.java
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public final class JacksonHelper {
private static final ObjectMapper MAPPER = new ObjectMapper();
private JacksonHelper() {}
public static String toJson(Object value) {
try {
return MAPPER.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
public static <T> T fromJson(String payload, Class<T> type) {
try {
return MAPPER.readValue(payload, type);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
JsonLibHelper.java
import net.sf.json.JSONObject;
public final class JsonLibHelper {
private JsonLibHelper() {}
public static String toJson(Object value) {
return JSONObject.fromObject(value).toString();
}
@SuppressWarnings("unchecked")
public static <T> T fromJson(String payload, Class<T> type) {
return (T) JSONObject.toBean(JSONObject.fromObject(payload), type);
}
}
Model types
import java.util.*;
import java.util.Date;
public class Person {
private String name;
private FullName fullName;
private int age;
private Date birthday;
private List<String> hobbies;
private Map<String, String> clothes;
private List<Person> friends;
// getters and setters omitted for brevity
@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append("Person{name=").append(name)
.append(", fullName=").append(fullName)
.append(", age=").append(age)
.append(", birthday=").append(birthday)
.append(", hobbies=").append(hobbies)
.append(", clothes=").append(clothes)
.append('\n');
if (friends != null && !friends.isEmpty()) {
b.append("Friends:\n");
for (Person f : friends) {
b.append('\t').append(f).append('\n');
}
}
return b.toString();
}
}
public class FullName {
private String firstName;
private String middleName;
private String lastName;
public FullName() {}
public FullName(String first, String middle, String last) {
this.firstName = first;
this.middleName = middle;
this.lastName = last;
}
// getters and setters omitted
@Override
public String toString() {
return "[firstName=" + firstName + ", middleName=" + middleName + ", lastName=" + lastName + ']';
}
}
Serialization microbenchmark
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.results.RunResult;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.*;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class JsonEncodeBench {
@Param({"1000", "10000", "100000"})
private int iterations;
private Person subject;
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(JsonEncodeBench.class.getSimpleName())
.forks(1)
.warmupIterations(0)
.build();
Collection<RunResult> results = new Runner(opt).run();
// Persist or visualize results via a custom sink if desired
// ResultsSink.emit("Encode", results, "iterations", "seconds");
}
@Benchmark
public void encodeWithJsonLib() {
for (int i = 0; i < iterations; i++) {
JsonLibHelper.toJson(subject);
}
}
@Benchmark
public void encodeWithGson() {
for (int i = 0; i < iterations; i++) {
GsonHelper.toJson(subject);
}
}
@Benchmark
public void encodeWithFastjson() {
for (int i = 0; i < iterations; i++) {
FastjsonHelper.toJson(subject);
}
}
@Benchmark
public void encodeWithJackson() {
for (int i = 0; i < iterations; i++) {
JacksonHelper.toJson(subject);
}
}
@Setup
public void setUp() {
List<Person> peers = new ArrayList<>();
peers.add(buildPerson("Xiao Ming", null));
peers.add(buildPerson("Tony", null));
peers.add(buildPerson("Chen Xiao er", null));
subject = buildPerson("Xiao Shu", peers);
}
private static Person buildPerson(String n, List<Person> peers) {
Person p = new Person();
p.setName(n);
p.setFullName(new FullName("zjj_first", "zjj_middle", "zjj_last"));
p.setAge(24);
List<String> likes = new ArrayList<>();
likes.add("Basketball");
likes.add("Swimming");
likes.add("coding");
p.setHobbies(likes);
Map<String, String> wear = new HashMap<>();
wear.put("coat", "Nike");
wear.put("trousers", "adidas");
wear.put("shoes", "Anta");
p.setClothes(wear);
p.setFriends(peers);
return p;
}
}
Observed behavior in typical runs shows:
- For smaller iteration counts, Gson often leads by a small margin.
- As the workolad scales (e.g., 100k serializations), Fastjson tends to overtake, with Jackson close behind.
- Jackson maintains consistently strong results acrosss scales.
- json-lib trails significantly in all cases.
Deserialization microbenchmark
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.results.RunResult;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class JsonDecodeBench {
@Param({"1000", "10000", "100000"})
private int iterations;
private String json;
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(JsonDecodeBench.class.getSimpleName())
.forks(1)
.warmupIterations(0)
.build();
Collection<RunResult> results = new Runner(opt).run();
// ResultsSink.emit("Decode", results, "iterations", "seconds");
}
@Benchmark
public void decodeWithJsonLib() {
for (int i = 0; i < iterations; i++) {
JsonLibHelper.fromJson(json, Person.class);
}
}
@Benchmark
public void decodeWithGson() {
for (int i = 0; i < iterations; i++) {
GsonHelper.fromJson(json, Person.class);
}
}
@Benchmark
public void decodeWithFastjson() {
for (int i = 0; i < iterations; i++) {
FastjsonHelper.fromJson(json, Person.class);
}
}
@Benchmark
public void decodeWithJackson() {
for (int i = 0; i < iterations; i++) {
JacksonHelper.fromJson(json, Person.class);
}
}
@Setup
public void setUp() {
json = "{" +
"\"name\":\"Xiao Shu\"," +
"\"fullName\":{\"firstName\":\"zjj_first\",\"middleName\":\"zjj_middle\",\"lastName\":\"zjj_last\"}," +
"\"age\":24," +
"\"birthday\":null," +
"\"hobbies\":[\"Basketball\",\"Swimming\",\"coding\"]," +
"\"clothes\":{\"shoes\":\"Anta\",\"trousers\":\"adidas\",\"coat\":\"Nike\"}," +
"\"friends\":[" +
"{\"name\":\"Xiao Ming\",\"fullName\":{\"firstName\":\"xxx_first\",\"middleName\":\"xxx_middle\",\"lastName\":\"xxx_last\"},\"age\":24,\"birthday\":null,\"hobbies\":[\"Basketball\",\"Swimming\",\"coding\"],\"clothes\":{\"shoes\":\"Anta\",\"trousers\":\"adidas\",\"coat\":\"Nike\"},\"friends\":null}," +
"{\"name\":\"Tony\",\"fullName\":{\"firstName\":\"xxx_first\",\"middleName\":\"xxx_middle\",\"lastName\":\"xxx_last\"},\"age\":24,\"birthday\":null,\"hobbies\":[\"Basketball\",\"Swimming\",\"coding\"],\"clothes\":{\"shoes\":\"Anta\",\"trousers\":\"adidas\",\"coat\":\"Nike\"},\"friends\":null}," +
"{\"name\":\"Chen Xiao er\",\"fullName\":{\"firstName\":\"xxx_first\",\"middleName\":\"xxx_middle\",\"lastName\":\"xxx_last\"},\"age\":24,\"birthday\":null,\"hobbies\":[\"Basketball\",\"Swimming\",\"coding\"],\"clothes\":{\"shoes\":\"Anta\",\"trousers\":\"adidas\",\"coat\":\"Nike\"},\"friends\":null}]" +
"}";
}
}
Typical outcomes for the decode benchmark indicate that Gson, Jackson, and Fastjson perform similarly and handle the workload efficiently, while json-lib remains notably slower. As always with microbenchmarks, absolute numbers depend on hardware, JVM, JIT optimizations, and library versions; relative trends above are stable under repeated runs on common setups.