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 ObjectMapper instance for each conversion.
public String getCarString(Car car) {
ObjectMapper objectMapper = new ObjectMapper();
String str = objectMapper.writeValueAsString(car);
return str;
}This pattern appears widely in CV engineering code and raises the question of whether repeatedly instantiating ObjectMapper hurts performance.
Is the Code Problematic?
Although the code works, creating a new ObjectMapper on every call wastes memory and may impact speed. ObjectMapper is 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.
@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();
}
}Benchmark results:
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/sThe single global ObjectMapper processes about 20 million JSON parses per second, roughly ten times faster than creating a new instance each time. Using a ThreadLocal gives 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.
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.
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.
