Why Reusing a Single Jackson ObjectMapper Boosts JSON Performance 10×
This article explains how repeatedly creating Jackson ObjectMapper instances hurts performance, demonstrates a JMH benchmark comparing new, singleton, and ThreadLocal ObjectMapper usages, and concludes that a single globally shared ObjectMapper can achieve up to ten times faster JSON parsing.
Background
Since the frequent security issues of fastjson, Jackson has become the default JSON library in the Spring ecosystem, but many developers notice that it lacks a simple
JSON.parseObject-style method and requires creating an
ObjectMapperinstance for each conversion.
<code>public String getCarString(Car car) {
ObjectMapper objectMapper = new ObjectMapper();
String str = objectMapper.writeValueAsString(car);
return str;
}
</code>This pattern appears widely in CV engineering code and raises the question of whether repeatedly instantiating
ObjectMapperhurts performance.
Is the Code Problematic?
Although the code works, creating a new
ObjectMapperon every call wastes memory and may impact speed.
ObjectMapperis thread‑safe and can be shared as a singleton.
Benchmark with JMH
To answer the performance question we use JMH (Java Microbenchmark Harness), a high‑precision benchmarking tool that can measure operations at the nanosecond level.
<code>@BenchmarkMode({Mode.Throughput})
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Thread)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@Threads(10)
public class ObjectMapperTest {
String json = "{\"color\":\"Black\",\"type\":\"BMW\"}";
@State(Scope.Benchmark)
public static class BenchmarkState {
ObjectMapper GLOBAL_MAP = new ObjectMapper();
ThreadLocal<ObjectMapper> GLOBAL_MAP_THREAD = new ThreadLocal<>();
}
@Benchmark
public Map globalTest(BenchmarkState state) throws Exception {
Map map = state.GLOBAL_MAP.readValue(json, Map.class);
return map;
}
@Benchmark
public Map globalTestThreadLocal(BenchmarkState state) throws Exception {
if (null == state.GLOBAL_MAP_THREAD.get()) {
state.GLOBAL_MAP_THREAD.set(new ObjectMapper());
}
Map map = state.GLOBAL_MAP_THREAD.get().readValue(json, Map.class);
return map;
}
@Benchmark
public Map localTest() throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
Map map = objectMapper.readValue(json, Map.class);
return map;
}
public static void main(String[] args) throws Exception {
Options opts = new OptionsBuilder()
.include(ObjectMapperTest.class.getSimpleName())
.resultFormat(ResultFormatType.CSV)
.build();
new Runner(opts).run();
}
}
</code>Benchmark results:
<code>Benchmark Mode Cnt Score Error Units
ObjectMapperTest.globalTest thrpt 5 25125094.559 ± 1754308.010 ops/s
ObjectMapperTest.globalTestThreadLocal thrpt 5 31780573.549 ± 7779240.155 ops/s
ObjectMapperTest.localTest thrpt 5 2131394.345 ± 216974.682 ops/s
</code>The single global
ObjectMapperprocesses about 20 million JSON parses per second, roughly ten times faster than creating a new instance each time. Using a
ThreadLocalgives a modest improvement but not a dramatic one.
Conclusion
For typical projects you should keep one globally shared
ObjectMapper. It is thread‑safe, reduces memory churn, and delivers an order‑of‑magnitude performance gain. Separate instances are only justified when you need distinct configuration for different use cases.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
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.