Backend Development 12 min read

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.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering Jackson ObjectMapper in Spring Boot 3: Real-World Cases & Performance Tips

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&lt;User&gt; 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&lt;String,Object&gt; :

<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&lt;User&gt; 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>&lt;dependency&gt;
  &lt;groupId&gt;com.fasterxml.jackson.module&lt;/groupId&gt;
  &lt;artifactId&gt;jackson-module-afterburner&lt;/artifactId&gt;
&lt;/dependency&gt;
</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.

Benchmark result
Benchmark result
JavaSerializationJSONSpring BootJacksondeserializationObjectMapper
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.