Jackson JSON Request and Response Mapping in Spring Boot
In this tutorial, we’ll learn how to map Jackson JSON request and response in Spring Boot Application with various Jackson configurations.
Jackson JSON ObjectMapper
When you create a @RestController
in a Spring Boot application to define API endpoints then Jackson JSON ObjectMapper is the default HTTP Converter of your web application which does two things:
-
Map the Java Object to JSON Response when you return the Object from GET request method like this:-
@GetMapping public List<User> getAllUsers()
Converting the Java Object to JSON is known as Marshalling or Serialization
-
Map the JSON to Java Object when you add a
@RequestBody
argument in POST request method like this:-@PostMapping public Long createUser(@RequestBody User user)
Converting the JSON to Java Object is known as Unmarshalling or Deserialization
We will look at various configurations of serialization and deserialization using Jackson ObjectMapper with examples that can help to resolve common issues related to JSON mapping in API development.
API Development
Let’s quickly develop some GET and POST APIs using the Controller class UserController
, we will use this to demonstrate various Jackson configurations and their impact on JSON.
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers();
}
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Long createUser(@RequestBody User user) {
return userService.createUser(user);
}
}
public class User {
private Long id;
private String name;
private LocalDate dateOfBirth;
/* Getters and Setters */
}
Let’s look at various important configurations and their impact on API requests and responses.
Prevent Failure on Unknown Property in JSON Request Body
If there are unknown properties in JSON Request Body that cannot be mapped to User
Java Object then Jackson ObjectMapper throws UnrecognizedPropertyException. This is a default behavior.
spring.jackson.deserialization.FAIL_ON_UNKNOWN_PROPERTIES = true (default)
Let’s add additional field gender
in POST request body which is missing in User
request mapping java Object. It will throw Json parse error Unrecognized field "gender" as in below example:-
application.yml
spring.jackson.deserialization.FAIL_ON_UNKNOWN_PROPERTIES: true
Request
curl -X POST \
http://localhost:8080/users \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-d '{
"id": 1,
"name": "Ashish",
"dateOfBirth": "1986-08-22",
"gender": "male"
}'
Response (Error)
JSON parse error: Unrecognized field "gender" (class com.example.api.model.User), not marked as ignorable;
nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "gender" (class com.example.api.model.User), not marked as ignorable (3 known properties: "dateOfBirth", "id", "name"])
We can prevent this failure by setting the FAIL_ON_UNKNOWN_PROPERTIES property to false and allowing unknown properties (or additional fields) in our JSON Request Body. If you are using Spring Boot default ObjectMapper then this is the default behavior.
spring.jackson.deserialization.FAIL_ON_UNKNOWN_PROPERTIES = false
We see that POST request did not fail this time and returned id
of created user.
application.yml
spring.jackson.deserialization.FAIL_ON_UNKNOWN_PROPERTIES: false
Request
curl -X POST \
http://localhost:8080/users \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-d '{
"id": 1,
"name": "Ashish",
"dateOfBirth": "1986-08-22",
"gender": "male"
}'
Response
1
You can also programmatically deserialize a JSON to Java Object with unknown properties using Jackson ObjectMapper like this:-
ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
User user = mapper.readValue(jsonAsString, User.class);
Don’t allow certain property in JSON Request Body
Sometimes we don’t want certain properties such as id
to be passed in the request body because we would be auto-generating that id
in the backend. In such case, you can annotate such properties with @JsonIgnore
.
@JsonIgnore Annotation
package com.example.api.model;
public class User {
@JsonIgnore
private Long id;
private String name;
private LocalDate dateOfBirth;
/* Getters and Setters */
}
spring.jackson.deserialization.FAIL_ON_IGNORED_PROPERTIES = false (default)
By default, Jackson ObjectMapper allows all the properties to be passed in the request body even those annotated with @JsonIgnore
. We see that it allows id
to be passed in the request body:-
application.yml
spring.jackson.deserialization.FAIL_ON_IGNORED_PROPERTIES: false
Request
curl -X POST \
http://localhost:8080/users \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-d '{
"id": 1,
"name": "Ashish",
"dateOfBirth": "1986-08-22"
}'
Response
1
spring.jackson.deserialization.FAIL_ON_IGNORED_PROPERTIES = true
Setting FAIL_ON_IGNORED_PROPERTIES
property to true will throw IgnoredPropertyException when id
property which is annotated with @JsonIgnore
is passed in request body:-
application.yml
spring.jackson.deserialization.FAIL_ON_IGNORED_PROPERTIES: true
Request
curl -X POST \
http://localhost:8080/users \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-d '{
"id": 1,
"name": "Ashish",
"dateOfBirth": "1986-08-22"
}'
Response (Error)
JSON parse error: Ignored field "id" (class com.example.api.model.User) encountered;
mapper configured not to allow this;
nested exception is com.fasterxml.jackson.databind.exc.IgnoredPropertyException:
Ignored field "id" (class com.example.api.model.User) encountered;
mapper configured not to allow this (3 known properties: "dateOfBirth", "name", "lastLogin"])
Don’t include properties with null value in JSON Response
We generally do not want to include the properties with NULL values in JSON response. It does make sense to exclude them to reduce the network load.
spring.jackson.default-property-inclusion: use_defaults (default)
Jackson includes the null properties in the JSON response by default.
application.yml
spring.jackson.default-property-inclusion: use_defaults
Request
curl -X GET http://localhost:8080/users
Response
[{"id":1,"name":"Adam","dateOfBirth":null},
{"id":2,"name":"Bob","dateOfBirth":null},
{"id":3,"name":"Charlie","dateOfBirth":null}]
spring.jackson.default-property-inclusion: non_null
We can set the application property spring.jackson.default-property-inclusion
to non_null
to not include null properties.
application.yml
spring.jackson.default-property-inclusion: non_null
Request
curl -X GET http://localhost:8080/users
Response
[{"id":1,"name":"Adam"},
{"id":2,"name":"Bob"},
{"id":3,"name":"Charlie"}]
@JsonInclude Annotation
For older versions of Spring boot, where the property spring.jackson.default-property-inclusion
doesn’t work, you can use @JsonInclude(Include.NON_NULL)
annotation at the class or field level.
package com.example.api.model;
public class User {
private Long id;
private String name;
@JsonInclude(JsonInclude.Include.NON_NULL)
private LocalDate dateOfBirth;
/* Getters and Setters */
}
Please note that field-level annotation overrides the class-level annotation, and class-level annotation overrides the application-level property.
Pretty Print JSON Response
Pretty print or formatted JSON response in the development environment makes it easier to read and debug API responses.
spring.jackson.serialization.INDENT_OUTPUT = false (default)
API JSON responses are not pretty print (formatted) by default.
application.yml
spring.jackson.serialization.INDENT_OUTPUT: false
Request
curl -X GET http://localhost:8080/users
Response
[{"id":1,"name":"Adam","dateOfBirth":"1950-01-01"},{"id":2,"name":"Bob","dateOfBirth":"1990-10-30"},{"id":3,"name":"Charlie","dateOfBirth":"1979-07-26"}]
spring.jackson.serialization.INDENT_OUTPUT = true
We can pretty print JSON Response by setting INDENT_OUTPUT
property to true
. We see that JSON response is formatted after that.
application.yml
spring.jackson.serialization.INDENT_OUTPUT: true
Request
curl -X GET http://localhost:8080/users
Response (Pretty Print)
[ {
"id" : 1,
"name" : "Adam",
"dateOfBirth" : "1950-01-01"
}, {
"id" : 2,
"name" : "Bob",
"dateOfBirth" : "1990-10-30"
}, {
"id" : 3,
"name" : "Charlie",
"dateOfBirth" : "1979-07-26"
} ]
Format Date and DateTime properties in JSON Response
Java Date and Time Objects such as Date
, LocalDate
, LocalDateTime
, ZonedDateTime
are converted into numeric timestamps by default during JSON serialization.
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = true (default)
application.yml
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS: true
Request
curl -X GET http://localhost:8080/users/1
Response
{
"id" : 1,
"name" : "Adam",
"dateOfBirth" : [ 1950, 1, 1 ],
"lastLogin" : [ 2020, 7, 3, 0, 26, 22, 211000000 ],
"zonedDateTime": 1622874073.231148
}
We can disable this behavior to allow Jackson to convert Date and Time types of objects into human-readable String format. Please note that if you are using Spring Boot’s default ObjectMapper then WRITE_DATES_AS_TIMESTAMPS property is set to false by default.
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false
application.yml
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS: false
Request
curl -X GET http://localhost:8080/users/1
Response
{
"id" : 1,
"name" : "Adam",
"dateOfBirth" : "1950-01-01",
"lastLogin" : "2020-07-03T00:28:32.394"
"zonedDateTime": "2021-06-05T14:22:45.066295+08:00"
}
We see that date and time fields are in human-readable format in JSON response after disabling this feature.
@JsonFormat Annotation
We can further customize the Date
, LocalDate
, LocalDateTime
, ZonedDateTime
Object types by annotating them with @JsonFormat
annotations in our User
Object Model.
package com.example.api.model;
public class User {
private Long id;
private String name;
@JsonFormat(pattern="dd MMM yyyy")
private LocalDate dateOfBirth;
@JsonFormat(pattern="dd MMM yyyy hh:mm:ss")
private LocalDateTime lastLogin;
@JsonFormat(pattern = "yyyy-MM-dd@HH:mm:ss.SSSXXX", locale = "en_SG", timezone = "Asia/Singapore")
private ZonedDateTime zonedDateTime;
/* Getters and Setters */
}
Output will be something like this after applying @JsonFormat
annotations:
application.yml
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS: false
Request
curl -X GET http://localhost:8080/users/1
Response
{
"id" : 1,
"name" : "Adam",
"dateOfBirth" : "01 Jan 1950",
"lastLogin" : "03 Jul 2020 01:03:34",
"zonedDateTime" : "2020-07-03@01:03:34.467+08:00"
}
Please note that once you apply @JsonFormat
annotation on Date and Time Object types, it is used in both serialization (object to JSON) and deserialization (JSON to object). That means you need to pass the date or time parameters in the same format in HTTP request body JSON if required.
@JsonSerialize Annotation
When you use @JsonFormat
on LocalDate
, LocalDateTime
and ZonedDateTime
then sometimes Jackson throw InvalidDefinitionException during serialization like this:-
Response (Error)
com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Java 8 date/time type `java.time.LocalDate` not supported by default:
add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: com.example.api.domain.User["dateOfBirth"])
You can prevent this issue by adding com.fasterxml.jackson.datatype:jackson-datatype-jsr310
dependency and using @JsonSerialize
annotation on date and time field types as below:-
public class User {
Long id;
String name;
@JsonFormat(pattern="dd MMM yyyy")
@JsonSerialize(using = LocalDateSerializer.class)
LocalDate dateOfBirth;
@JsonFormat(pattern="dd MMM yyyy hh:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
LocalDateTime lastLogin;
@JsonFormat(pattern = "yyyy-MM-dd@HH:mm:ss.SSSXXX", locale = "en_SG", timezone = "Asia/Singapore")
@JsonSerialize(using = ZonedDateTimeSerializer.class)
ZonedDateTime zonedDateTime;
}
Conclusion
We looked at some of the useful configurations. Here is the full list of Jackson serialization and deserialization properties configurable in Spring Boot Application using application.yml, or application.properties file.
application.yml
spring:
jackson:
default-property-inclusion: use_defaults/always/non-null/non-empty/non-absent/non-default
serialization:
CLOSE_CLOSEABLE: true/false
EAGER_SERIALIZER_FETCH: true/false
FAIL_ON_EMPTY_BEANS: true/false
FAIL_ON_SELF_REFERENCES: true/false
FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS: true/false
FLUSH_AFTER_WRITE_VALUE: true/false
INDENT_OUTPUT: true/false
ORDER_MAP_ENTRIES_BY_KEYS: true/false
USE_EQUALITY_FOR_OBJECT_ID: true/false
WRAP_EXCEPTIONS: true/false
WRAP_ROOT_VALUE: true/false
WRITE_BIGDECIMAL_AS_PLAIN: true/false
WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS: true/false
WRITE_DATES_AS_TIMESTAMPS: true/false
WRITE_DATES_WITH_ZONE_ID: true/false
WRITE_DATE_KEYS_AS_TIMESTAMPS: true/false
WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS: true/false
WRITE_DURATIONS_AS_TIMESTAMPS: true/false
WRITE_EMPTY_JSON_ARRAYS: true/false
WRITE_ENUMS_USING_INDEX: true/false
WRITE_ENUMS_USING_TO_STRING: true/false
WRITE_ENUM_KEYS_USING_INDEX: true/false
WRITE_NULL_MAP_VALUES: true/false
WRITE_SELF_REFERENCES_AS_NULL: true/false
WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED: true/false
deserialization:
ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT: true/false
ACCEPT_EMPTY_STRING_AS_NULL_OBJECT: true/false
ACCEPT_FLOAT_AS_INT: true/false
ACCEPT_SINGLE_VALUE_AS_ARRAY: true/false
ADJUST_DATES_TO_CONTEXT_TIME_ZONE: true/false
EAGER_DESERIALIZER_FETCH: true/false
FAIL_ON_IGNORED_PROPERTIES: true/false
FAIL_ON_INVALID_SUBTYPE: true/false
FAIL_ON_MISSING_CREATOR_PROPERTIES: true/false
FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY: true/false
FAIL_ON_NULL_CREATOR_PROPERTIES: true/false
FAIL_ON_NULL_FOR_PRIMITIVES: true/false
FAIL_ON_NUMBERS_FOR_ENUMS: true/false
FAIL_ON_READING_DUP_TREE_KEY: true/false
FAIL_ON_TRAILING_TOKENS: true/false
FAIL_ON_UNKNOWN_PROPERTIES: true/false
FAIL_ON_UNRESOLVED_OBJECT_IDS: true/false
READ_DATE_TIMESTAMPS_AS_NANOSECONDS: true/false
READ_ENUMS_USING_TO_STRING: true/false
READ_UNKNOWN_ENUM_VALUES_AS_NULL:true/false
READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE: true/false
UNWRAP_ROOT_VALUE: true/false
UNWRAP_SINGLE_VALUE_ARRAYS: true/false
USE_BIG_DECIMAL_FOR_FLOATS: true/false
USE_BIG_INTEGER_FOR_INTS: true/false
USE_JAVA_ARRAY_FOR_JSON_ARRAY: true/false
USE_LONG_FOR_INTS: true/false
WRAP_EXCEPTIONS: true/false
You can refer to the Spring Boot official documentation for the customization of Jackson ObjectMapper.
Please note that spring boot configuration supports Relaxed Binding which means properties can be in uppercase or lowercase, both are valid.
spring.jackson.serialization.INDENT_OUTPUT: true
is same as
spring.jackson.serialization.indent_output: true
That’s it for now. I’ll keep updating this post with more practical use cases as I come across them.
Download the source code for these examples from github/springboot-api