Mastering Jackson ObjectMapper in Spring Boot 3: Real-World Cases & Performance Tips
This article explores Jackson's ObjectMapper in Spring Boot 3, covering basic serialization/deserialization, converting between objects, JSON strings, JsonNode, and collections, custom serializers/deserializers, handling unknown fields, and performance tuning with the Afterburner module and JMH benchmarks.
Introduction
ObjectMapper is the core component of the Jackson library, providing efficient conversion between Java objects and JSON. It supports all Java types, offers rich configuration (naming strategies, date formats, null handling), and can be extended with modules such as JavaTimeModule and streaming APIs ( JsonGenerator / JsonParser ).
Basic Read/Write Operations
Typical usage relies on readValue and writeValue methods.
<code>public record User(Long id, String name, int age, String email) {}</code> <code>ObjectMapper objectMapper = new ObjectMapper();
User user = new User(666L, "Spring实战案例源码", 33, "[email protected]");
objectMapper.writeValue(System.out, user);
// Output: {"id":666,"name":"Spring实战案例源码","age":33,"email":"[email protected]"}</code>String JSON can be obtained with writeValueAsString :
<code>String json = objectMapper.writeValueAsString(user);
System.out.println(json);
// {"id":666,"name":"Spring实战案例源码","age":33,"email":"[email protected]"}</code>Deserialization from a JSON string:
<code>String jsonStr = "{"id":666,"name":"Spring实战案例源码","age":33,"email":"[email protected]"}";
User u = objectMapper.readValue(jsonStr, User.class);
System.out.println(u);
// User[id=666, name=Spring实战案例源码, age=33, [email protected]]</code>Reading from a URL:
<code>User u = objectMapper.readValue(URI.create("http://localhost:8080/user.json").toURL(), User.class);
</code>Parsing to JsonNode :
<code>JsonNode node = objectMapper.readTree(json);
String name = node.get("name").asText();
String title = node.get("info").get("title").asText();
System.out.println(name + "@" + title);
// Spring实战案例源码@高级架构师</code>Converting a JSON array to a List<User> using TypeReference :
<code>String jsonArray = "[{'id':666,'name':'Spring实战案例源码','age':33,'email':'[email protected]'},{'id':888,'name':'Pack_xg','age':22,'email':'[email protected]'}]";
List<User> users = objectMapper.readValue(jsonArray, new TypeReference<List<User>>(){});
System.out.println(users);
</code>Converting JSON to a Map<String,Object> :
<code>Map<String,Object> map = objectMapper.readValue(json, new TypeReference<Map<String,Object>>(){});
System.out.println(map);
</code>Advanced Features
Custom Serialization & Deserialization
When JSON contains unknown fields, configure the mapper to ignore them:
<code>ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
</code>Other useful features include FAIL_ON_NULL_FOR_PRIMITIVES and FAIL_ON_NUMBERS_FOR_ENUMS .
Custom serializer example (only id and name are output):
<code>public class CustomUserSerializer extends StdSerializer<User> {
public CustomUserSerializer() { this(null); }
public CustomUserSerializer(Class<User> t) { super(t); }
@Override
public void serialize(User user, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeNumberField("id", user.id());
gen.writeStringField("name", user.name());
gen.writeEndObject();
}
}
</code>Testing the custom serializer:
<code>ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("CustomUserSerializer", new Version(1,0,0,null,null,null));
module.addSerializer(User.class, new CustomUserSerializer());
mapper.registerModule(module);
System.out.println(mapper.writeValueAsString(user));
// {"id":666,"name":"Spring实战案例源码"}
</code>Custom deserializer example (ignores extra fields):
<code>public class CustomUserDeserializer extends StdDeserializer<User> {
public CustomUserDeserializer() { this(null); }
public CustomUserDeserializer(Class<?> vc) { super(vc); }
@Override
public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode node = p.getCodec().readTree(p);
return new User(node.get("id").asLong(), node.get("name").asText(), null, null);
}
}
</code>Testing the custom deserializer:
<code>ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("CustomUserDeserializer", new Version(1,0,0,null,null,null));
module.addDeserializer(User.class, new CustomUserDeserializer());
mapper.registerModule(module);
User u = mapper.readValue(jsonStr, User.class);
System.out.println(u);
// User[id=666, name=Spring实战案例源码, age=null, email=null]
</code>Collection Handling
Using DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY to map a JSON array directly to a Java array:
<code>ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY, true);
User[] users = mapper.readValue(jsonArray, User[].class);
System.out.println(Arrays.toString(users));
</code>Reading a JSON array into a List<User> with TypeReference is also demonstrated above.
Performance Optimization
The jackson-module-afterburner module generates bytecode for serialization/deserialization, reducing runtime overhead for large JSON workloads.
<code><dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-afterburner</artifactId>
</dependency>
</code> <code>@Configuration
public class JacksonConfig {
@Bean
public AfterburnerModule afterburnerModule() {
return new AfterburnerModule();
}
}
</code>JMH benchmark comparing a plain ObjectMapper with one that registers AfterburnerModule shows only a modest speed difference for typical objects.
<code>@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
@Warmup(iterations = 5, time = 3)
@Measurement(iterations = 5, time = 3)
@Fork(1)
public class ObjectMapperWithAndWithoutTest {
private static final ObjectMapper with = new ObjectMapper().registerModule(new AfterburnerModule());
private static final ObjectMapper without = new ObjectMapper();
private static final User testUser = new User(666L, "Spring实战案例源码", 33, "[email protected]");
@Benchmark
public String withoutSerialize() throws Exception { return without.writeValueAsString(testUser); }
@Benchmark
public String withSerialize() throws Exception { return with.writeValueAsString(testUser); }
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder().include(ObjectMapperWithAndWithoutTest.class.getSimpleName()).build();
new Runner(opt).run();
}
}
</code>Benchmark results (illustrated below) indicate the performance gap is not significant for most use cases.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.