Jackson and JSON in Java, finally learn with a coding-first approach:
>> Download the eBookMocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
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.
Further reading:
Spring JSON-P with Jackson
How to Set JSON Content Type in Spring MVC
Intro to the Jackson ObjectMapper
2. Default Configuration
By default, the Spring Boot configuration will disable the following:
- MapperFeature.DEFAULT_VIEW_INCLUSION
- DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
- 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(name = "brand", required = false) String brand,
@RequestParam(name = "name", required = false) String name) {
return new Coffee()
.setBrand(brand)
.setDate(FIXED_DATE)
.setName(name);
}
By default, this will be the response when calling GET http://lolcahost:8080/coffee?brand=Lavazza:
{
"name": null,
"brand": "Lavazza",
"date": "2020-11-16T10:21:35.974"
}
We want to exclude null values and have a custom date format (dd-MM-yyyy HH:mm). This is our final response:
{
"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 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.
Hereβs the general structure of the configuration:
spring.jackson.<category_name>.<feature_name>=true,false
As an example, hereβs what weβll add to disable SerializationFeature.WRITE_DATES_AS_TIMESTAMPS:
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 this 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 the following:
spring.jackson.default-property-inclusion=non_null
Spring Boot will automatically register any bean of type com.fasterxml.jackson.databind.Module. Hereβs our final result:
{
"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 donβt want to allow external configuration.
4.2. Jackson2ObjectMapperBuilder
Another clean approach is to define a Jackson2ObjectMapperBuilder bean.
Spring Boot actually uses 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 article, 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.
