Spring Boot: Customize the Jackson ObjectMapper

1. Overview

When using JSON format, Spring Boot will use an ObjectMapper instance to serialize responses and deserialize requests. In this tutorial, we’ll take a look at the most common ways to configure the serialization and deserialization options.

To learn more about Jackson, be sure to check out our Jackson tutorial.

2. Default Configuration

By default, the Spring Boot configuration will:

  • Disable MapperFeature.DEFAULT_VIEW_INCLUSION
  • Disable DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
  • Disable SerializationFeature.WRITE_DATES_AS_TIMESTAMPS

Let’s start with a quick example:

  • The client will send a GET request to our /coffee?name=Lavazza
  • The controller will return a new Coffee object
  • Spring will use ObjectMapper to serialize our POJO to JSON

We’ll exemplify the customization options by using String and LocalDateTime objects:

public class Coffee {

    private String name;
    private String brand;
    private LocalDateTime date;

   //getters and setters
}

We’ll also define a simple REST controller to demonstrate the serialization:

@GetMapping("/coffee")
public Coffee getCoffee(
        @RequestParam(required = false) String brand,
        @RequestParam(required = false) String name) {
    return new Coffee()
      .setBrand(brand)
      .setDate(FIXED_DATE)
      .setName(name);
}

By default, the response when calling GET http://lolcahost:8080/coffee?brand=Lavazza will be:

{
  "name": null,
  "brand": "Lavazza",
  "date": "2020-11-16T10:21:35.974"
}

We would like to exclude null values and to have a custom date format (dd-MM-yyyy HH:mm). The final response will be:

{
  "brand": "Lavazza",
  "date": "04-11-2020 10:34"
}

When using Spring Boot, we have the option to customize the default ObjectMapper or to override it. We’ll cover both of these options in the next sections.

3. Customizing the Default ObjectMapper

In this section, we’ll see how to customize the default ObjectMapper that Spring Boot uses.

3.1. Application Properties and Custom Jackson Module

The simplest way to configure the mapper is via application properties. The general structure of the configuration is:

spring.jackson.<category_name>.<feature_name>=true,false

As an example, if we want to disable SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, we’ll add:

spring.jackson.serialization.write-dates-as-timestamps=false

Besides the mentioned feature categories, we can also configure property inclusion:

spring.jackson.default-property-inclusion=always, non_null, non_absent, non_default, non_empty

Configuring the environment variables is the simplest approach. The downside of this approach is that we can’t customize advanced options like having a custom date format for LocalDateTime. At this point, we’ll obtain the result:

{
  "brand": "Lavazza",
  "date": "2020-11-16T10:35:34.593"
}

In order to achieve our goal, we’ll register a new JavaTimeModule with our custom date format:

@Configuration
@PropertySource("classpath:coffee.properties")
public class CoffeeRegisterModuleConfig {

    @Bean
    public Module javaTimeModule() {
        JavaTimeModule module = new JavaTimeModule();
        module.addSerializer(LOCAL_DATETIME_SERIALIZER);
        return module;
    }
}

Also, the configuration properties file coffee.properties will contain:

spring.jackson.default-property-inclusion=non_null

Spring Boot will automatically register any bean of type com.fasterxml.jackson.databind.Module. The final result will be:

{
  "brand": "Lavazza",
  "date": "16-11-2020 10:43"
}

3.2. Jackson2ObjectMapperBuilderCustomizer

The purpose of this functional interface is to allow us to create configuration beans. They will be applied to the default ObjectMapper created via Jackson2ObjectMapperBuilder:

@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
    return builder -> builder.serializationInclusion(JsonInclude.Include.NON_NULL)
      .serializers(LOCAL_DATETIME_SERIALIZER);
}

The configuration beans are applied in a specific order, which we can control using the @Order annotation. This elegant approach is suitable if we want to configure the ObjectMapper from different configurations or modules.

4. Overriding the Default Configuration

If we want to have full control over the configuration, there are several options that will disable the auto-configuration and allow only our custom configuration to be applied. Let’s take a close look at these options.

4.1. ObjectMapper

The simplest way to override the default configuration is to define an ObjectMapper bean and to mark it as @Primary:

@Bean
@Primary
public ObjectMapper objectMapper() {
    JavaTimeModule module = new JavaTimeModule();
    module.addSerializer(LOCAL_DATETIME_SERIALIZER);
    return new ObjectMapper()
      .setSerializationInclusion(JsonInclude.Include.NON_NULL)
      .registerModule(module);
}

We should use this approach when we want to have full control over the serialization process and we don’t want to allow external configuration.

4.2. Jackson2ObjectMapperBuilder

Another clean approach is to define a Jackson2ObjectMapperBuilder bean. Actually, Spring Boot is using this builder by default when building the ObjectMapper and will automatically pick up the defined one:

@Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
    return new Jackson2ObjectMapperBuilder().serializers(LOCAL_DATETIME_SERIALIZER)
      .serializationInclusion(JsonInclude.Include.NON_NULL);
}

It will configure two options by default:

  • Disable MapperFeature.DEFAULT_VIEW_INCLUSION
  • Disable DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES

According to the Jackson2ObjectMapperBuilder documentation, it will also register some modules if they’re present on the classpath:

  • jackson-datatype-jdk8: support for other Java 8 types like Optional
  • jackson-datatype-jsr310: support for Java 8 Date and Time API types
  • jackson-datatype-joda: support for Joda-Time types
  • jackson-module-kotlin: support for Kotlin classes and data classes

The advantage of this approach is that the Jackson2ObjectMapperBuilder offers a simple and intuitive way to build an ObjectMapper.

4.3. MappingJackson2HttpMessageConverter

We can just define a bean with type MappingJackson2HttpMessageConverter, and Spring Boot will automatically use it:

@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder().serializers(LOCAL_DATETIME_SERIALIZER)
      .serializationInclusion(JsonInclude.Include.NON_NULL);
    return new MappingJackson2HttpMessageConverter(builder.build());
}

Be sure to check out our Spring Http Message Converters article to learn more.

5. Testing the Configuration

To test our configuration, we’ll use TestRestTemplate and serialize the objects as String. In this way, we can validate that our Coffee object is serialized without null values and with the custom date format:

@Test
public void whenGetCoffee_thenSerializedWithDateAndNonNull() {
    String formattedDate = DateTimeFormatter.ofPattern(CoffeeConstants.dateTimeFormat).format(FIXED_DATE);
    String brand = "Lavazza";
    String url = "/coffee?brand=" + brand;
    
    String response = restTemplate.getForObject(url, String.class);
    
    assertThat(response).isEqualTo("{\"brand\":\"" + brand + "\",\"date\":\"" + formattedDate + "\"}");
}

6. Conclusion

In this tutorial, we took a look at several methods to configure the JSON serialization options when using Spring Boot.

We saw two different approaches: configuring the default options or overriding the default configuration.

As always, the full source code of the article is available over on GitHub.

Related posts:

Convert a Map to an Array, List or Set in Java
Transactions with Spring and JPA
SOAP Web service: Upload và Download file sử dụng MTOM trong JAX-WS
Java equals() and hashCode() Contracts
HttpAsyncClient Tutorial
Generate Spring Boot REST Client with Swagger
How to Define a Spring Boot Filter?
“Stream has already been operated upon or closed” Exception in Java
The SpringJUnitConfig and SpringJUnitWebConfig Annotations in Spring 5
Java Program to Perform Preorder Recursive Traversal of a Given Binary Tree
Introduction to Spring Cloud Netflix – Eureka
Java Program to Implement AA Tree
Guide to Selenium with JUnit / TestNG
Java Program to Implement Dijkstra’s Algorithm using Priority Queue
Java Program to Find Strongly Connected Components in Graphs
Exception Handling in Java
Vấn đề Nhà sản xuất (Producer) – Người tiêu dùng (Consumer) và đồng bộ hóa các luồng trong Java
Java Program to Check Whether an Undirected Graph Contains a Eulerian Path
Configure a RestTemplate with RestTemplateBuilder
Introduction to the Functional Web Framework in Spring 5
Adding a Newline Character to a String in Java
Java Program to Implement JobStateReasons API
Migrating from JUnit 4 to JUnit 5
Convert char to String in Java
Java Program to Find the Edge Connectivity of a Graph
Java Program to Implement an Algorithm to Find the Global min Cut in a Graph
Abstract class và Interface trong Java
Java Program to Implement Heap’s Algorithm for Permutation of N Numbers
Java Program to find the maximum subarray sum using Binary Search approach
The Registration API becomes RESTful
Java – Delete a File
Easy Ways to Write a Java InputStream to an OutputStream