Performance Comparison of ObjectMapper Instantiation Strategies Using JMH
This article benchmarks three ObjectMapper usage patterns—creating a new instance per call, sharing a global singleton, and using ThreadLocal—demonstrating that a single shared ObjectMapper can achieve up to ten‑fold higher throughput in JSON parsing.
Since the frequent failures of the domestic fastjson library, Jackson has become the default JSON processor in the Spring ecosystem, leading to widespread use across Java projects.
Unlike fastjson, Jackson does not provide a one‑line JSON.parseObject method; developers typically instantiate an ObjectMapper each time they need to serialize or deserialize JSON, as shown below:
public String getCarString(Car car) {
ObjectMapper objectMapper = new ObjectMapper();
String str = objectMapper.writeValueAsString(car);
return str;
}While this code works, creating a new ObjectMapper on every call can be wasteful in terms of memory allocation and execution time, especially for performance‑critical applications.
To investigate the impact, we use JMH (Java Microbenchmark Harness), a high‑precision benchmarking tool that can measure code performance down to nanoseconds. JMH integrates with JUnit‑style annotations such as @Benchmark , @State , @Warmup , and @Measurement , allowing us to configure forks, threads, and iteration counts.
The benchmark defines three scenarios:
Instantiate a new ObjectMapper inside the method.
Share a single global ObjectMapper instance.
Use a ThreadLocal to give each thread its own ObjectMapper .
All tests are CPU‑bound, running on a 10‑core machine with ten concurrent threads.
@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
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();
}
}The benchmark results are:
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/sThese numbers show that re‑creating an ObjectMapper for each call yields about 2 million operations per second, while reusing a single global instance reaches over 20 million ops/s—a ten‑fold improvement. The ThreadLocal approach offers a modest gain over the local instance but does not match the global singleton.
Therefore, in typical projects you should maintain a single, thread‑safe ObjectMapper instance (or a small number of specially configured instances) rather than constructing a new one for every JSON operation.
IT Services Circle
Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.
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.