Is Creating a New ObjectMapper Every Call Killing Your Performance? A JMH Benchmark
This article examines the performance impact of instantiating a new Jackson ObjectMapper for each JSON parsing operation versus reusing a global instance, using JMH micro‑benchmarks to show that a shared ObjectMapper can be up to ten times faster.
Since the frequent failures of fastjson, Jackson JSON library has become increasingly popular, especially as the default in the Spring family.
Many developers notice that Jackson lacks a fast
JSON.parseObject-like method, so they instantiate a new ObjectMapper for each parsing.
<code>public String getCarString(Car car) {
ObjectMapper objectMapper = new ObjectMapper();
String str = objectMapper.writeValueAsString(car);
return str;
}</code>This pattern is common among CV engineers, but its performance impact is questionable.
Is This Code Problematic?
Although the code works, creating a new ObjectMapper each call wastes memory and may affect execution time. ObjectMapper is thread‑safe and can be shared as a singleton.
To measure the impact, we use JMH (Java Microbenchmark Harness), a high‑precision benchmarking tool that can measure down to nanoseconds.
JMH Test Results
We benchmark three scenarios:
Instantiate ObjectMapper inside the method.
Use a globally shared ObjectMapper.
Use a ThreadLocal ObjectMapper per thread.
<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 output:
<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 results show that creating a new ObjectMapper per call yields about 2 million ops/s, while a shared global instance reaches over 20 million ops/s—a ten‑fold improvement. ThreadLocal provides a modest gain but not dramatic.
Conclusion
In most projects you should keep a single global ObjectMapper. It is thread‑safe, reduces memory allocation, and delivers far better performance. Only create separate instances when you need distinct configurations.
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.