Jackson ObjectMapper
About
ObjectMapper
is a core class provided by the Jackson library, used for converting between Java objects and JSON. It allows us to:
Serialize Java objects to JSON
Deserialize JSON to Java objects
Work with Maps, Lists, and generic types
Configure inclusion/exclusion rules
Register modules for special data types like Java 8 date/time, Java records, etc.
Although Jackson is not a Spring-native library, Spring Boot auto-configures Jackson and provides seamless integration out-of-the-box, making ObjectMapper
one of the most commonly used utilities in Spring-based applications.
Dependency
If we are using Spring Boot with web or REST modules, Jackson is included automatically via the spring-boot-starter-web
dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
This transitively includes:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
If we are not using Spring Boot or want to include Jackson manually, add:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version> <!-- Use latest stable -->
</dependency>
Spring Integration
How Spring Boot Uses Jackson Internally ?
Spring Boot uses Jackson as the default JSON processor. When we build a REST API or web application with spring-boot-starter-web
, it:
Automatically includes the Jackson library (
jackson-databind
)Registers a singleton
ObjectMapper
bean in the Spring contextUses that bean for:
Serializing controller response objects (
@ResponseBody
)Deserializing request bodies into Java objects (
@RequestBody
)JSON conversion in
RestTemplate
,WebClient
, and Feign clients
We can inject and use this same ObjectMapper throughout our application:
@Autowired
private ObjectMapper objectMapper;
Default Behavior in Spring Boot
When using Spring Boot, the default ObjectMapper
is configured with sensible defaults.
Example Defaults
Include null
fields in JSON
Yes
Pretty-print JSON
No
Fail on unknown JSON properties
No (FAIL_ON_UNKNOWN_PROPERTIES
is disabled)
Support for Java 8 Date/Time types
Yes (via jackson-datatype-jsr310
)
Use of ISO-8601 for date serialization
Yes
Support for Java 8 Optional types
Yes
Snake case support
No (uses camelCase by default)
We can override these behaviors either:
Through application properties
By providing a custom
ObjectMapper
bean
Customizing ObjectMapper via application.yml
application.yml
spring:
jackson:
# Include strategy for properties
# Options: always, non_null, non_absent, non_default, non_empty
default-property-inclusion: non_null # Exclude null fields from JSON output
# Serialization settings
serialization:
indent_output: true # Pretty-print the JSON output (adds newlines and spaces)
# Deserialization settings
deserialization:
fail-on-unknown-properties: false # Do not fail if JSON has unknown fields
# Date and time formatting
date-format: yyyy-MM-dd'T'HH:mm:ss # Custom global date-time format
time-zone: Asia/Kolkata # Time zone to use for date/time serialization
# Naming strategy for property names
# Options: SNAKE_CASE, LOWER_CAMEL_CASE (default), UPPER_CAMEL_CASE, KEBAB_CASE, etc.
property-naming-strategy: SNAKE_CASE # Converts Java fields like "firstName" to "first_name"
# Enable/disable features (serialization/deserialization-level)
mapper:
# Allow use of getter methods as creators (if no constructor present)
allow-getters-as-setters: true
# Visibility control (used less frequently)
visibility:
# Property-level visibility configuration
field: any # Detect all fields regardless of access modifier
getter: none # Ignore getters
is-getter: none # Ignore "isX()" methods for booleans
setter: any # Detect all setters
creator: any # Detect constructors and factory methods
Internally, Spring Boot does this
Spring Boot binds these properties into a Jackson2ObjectMapperBuilder
and applies them when initializing the ObjectMapper
bean.
This is equivalent to doing this programmatically:
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"));
Overriding the Default ObjectMapper
If we need full control, define our own @Bean
of type ObjectMapper
. Spring Boot will use our version instead of the default.
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper customObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.registerModule(new JavaTimeModule());
mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
return mapper;
}
}
Note: If we do this, make sure to register required modules like
JavaTimeModule
, otherwise date/time parsing may break.
Use Cases
1. Serialization: Java Object to JSON
Convert a Java object into a JSON string.
Person person = new Person("John", 30);
String json = objectMapper.writeValueAsString(person);
Write to file or output stream:
objectMapper.writeValue(new File("person.json"), person);
Pretty-printing:
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
String prettyJson = objectMapper.writeValueAsString(person);
2. Deserialization: JSON to Java Object
Convert a JSON string to a Java object.
String json = "{\"name\":\"John\",\"age\":30}";
Person person = objectMapper.readValue(json, Person.class);
From file:
Person person = objectMapper.readValue(new File("person.json"), Person.class);
3. Convert Between Java Objects (Object to Object Mapping)
Convert one type to another using intermediate JSON conversion. Useful for mapping DTOs to entities.
UserDTO dto = new UserDTO("john.doe", "Admin");
UserEntity entity = objectMapper.convertValue(dto, UserEntity.class);
4. Convert to and from Map
POJO Class
public class User {
private String name;
private int age;
// Constructors, getters, setters
}
Convert POJO to Map
User user = new User("Alice", 25);
Map<String, Object> map = objectMapper.convertValue(user, new TypeReference<>() {});
System.out.println(map);
{name=Alice, age=25}
Convert Map to POJO
<String, Object> map = new HashMap<>();
map.put("name", "Bob");
map.put("age", 30);
User user = objectMapper.convertValue(map, User.class);
System.out.println(user.getName()); // Bob
System.out.println(user.getAge()); // 30
5. Handling Collections and Generics
Deserialize a JSON array into a list of objects:
String json = "[{\"name\":\"John\",\"age\":30},{\"name\":\"Jane\",\"age\":25}]";
List<Person> people = objectMapper.readValue(
json, new TypeReference<List<Person>>() {}
);
Deserialize a nested structure:
String json = "{\"group\":\"team1\",\"members\":[{\"name\":\"John\"},{\"name\":\"Jane\"}]}";
Group group = objectMapper.readValue(json, Group.class);
6. Deserialization with Unknown or Partial JSON
POJO Class
public class Product {
private String name;
private double price;
// Constructors, getters, setters
}
JSON with Extra Field (unknown)
String json = """
{
"name": "Laptop",
"price": 999.99,
"warranty": "2 years" // Not present in Product class
}
""";
Default Behavior (in Spring Boot)
By default, Spring Boot configures ObjectMapper
to ignore unknown fields, so this will work fine.
Product product = objectMapper.readValue(json, Product.class);
System.out.println(product.getName()); // Laptop
System.out.println(product.getPrice()); // 999.99
Manually Ensuring it Ignores Unknown Fields
If we are not in Spring Boot, or just want to be sure:
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Partial JSON
String partialJson = """
{
"name": "Phone"
}
""";
If price
is missing, it will be set to its default value (e.g., 0.0
for double
).
Product product = objectMapper.readValue(partialJson, Product.class);
System.out.println(product.getName()); // Phone
System.out.println(product.getPrice()); // 0.0
Serialization Inclusion Strategies
Serialization inclusion strategies determine what data gets included in the JSON output. This is important to reduce unnecessary data in API responses, especially when dealing with large objects or REST services.
Options
Include.ALWAYS
Always include the property (default)
Include.NON_NULL
Exclude null
values
Include.NON_EMPTY
Exclude empty values (empty string, empty list, etc.)
Include.NON_DEFAULT
Exclude fields with default values
Include.NON_ABSENT
Exclude nulls and Java 8 Optional.empty()
Global Configuration
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
Field-Level Configuration
public class User {
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<String> tags;
}
Spring Boot Configuration (application.yml)
spring:
jackson:
default-property-inclusion: non_null
Use Case Example
You may not want to return null or empty fields to reduce response payload size:
{
"name": "John",
"email": null
}
With NON_NULL
, email
will be excluded.
Date and Time Handling
Working with dates and times in Java can be tricky, especially when serializing and deserializing JSON in REST APIs. Jackson provides powerful capabilities for formatting and parsing date/time types, and Spring Boot makes this integration seamless—but only if you configure it correctly.
Why Special Handling Is Needed
Java 8 introduced a new Date-Time API under java.time.*
packages like:
LocalDate
LocalDateTime
ZonedDateTime
Instant
These types are not supported natively by Jackson without additional configuration. Jackson treats them differently than older types like java.util.Date
, and without proper setup, they are either:
Serialized as arrays (e.g.
[2024, 6, 20]
)Serialized as timestamps
Or cause deserialization errors
Solution
To handle Java 8 time types correctly:
Register
JavaTimeModule
Disable writing as timestamps
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
This enables human-readable ISO-8601 strings like:
"2024-06-20T15:45:00"
instead of:
[2024, 6, 20, 15, 45, 0]
Field-Level Formatting
Use @JsonFormat
to define a custom date format for individual fields:
public class Event {
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate date;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateTime;
}
This ensures consistent formatting even if the global ObjectMapper
is shared across the application.
Global Formatting in Spring Boot
Spring Boot allows configuring date/time formats centrally using application.yml
or application.properties
.
YAML Configuration
spring:
jackson:
date-format: yyyy-MM-dd'T'HH:mm:ss
time-zone: Asia/Kolkata
Effect
This:
Sets the format for all
java.util.Date
andCalendar
Does not apply to Java 8
java.time.*
types unless you registerJavaTimeModule
To apply it for both, combine Spring config +
JavaTimeModule
registration.
Timezone Handling
When serializing or deserializing time-zone-aware types like ZonedDateTime
or OffsetDateTime
, Jackson can:
Use default system time zone
Apply a specific time zone via config
Respect explicit time zone in the string
Set Global Time Zone
mapper.setTimeZone(TimeZone.getTimeZone("Asia/Kolkata"));
Or via Spring config:
spring:
jackson:
time-zone: Asia/Kolkata
Deserialization Considerations
When parsing strings, Jackson expects them to match the format defined by:
@JsonFormat
(if present)application.yml
format (if configured)ISO-8601 (default fallback for
JavaTimeModule
)
Mismatched formats will cause JsonParseException
.
Field Naming Strategy
Field naming strategies control how Java field names are mapped to JSON keys.
Default: camelCase
firstName → "firstName"
snake_case:
mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
firstName → "first_name"
Spring Boot Configuration
spring:
jackson:
property-naming-strategy: SNAKE_CASE
Use Case
Aligns backend field names with frontend naming conventions, especially in REST APIs.
Working with JSON Tree (JsonNode)
Use JsonNode
when working with dynamic, partially known, or schema-less JSON structures.
Parsing JSON as Tree
String json = "{\"name\":\"Alice\", \"age\":30}";
JsonNode root = objectMapper.readTree(json);
String name = root.get("name").asText(); // "Alice"
int age = root.get("age").asInt(); // 30
Modifying Tree
((ObjectNode) root).put("name", "Bob");
Traversing Nested Nodes
JsonNode address = root.path("address");
String city = address.path("city").asText();
Creating a Tree Manually
ObjectNode node = objectMapper.createObjectNode();
node.put("username", "john");
node.put("active", true);
Use Case
Perfect for generic filters, form submissions, or when schema is not static.
Annotations for Fine-Grained Control
Jackson provides annotations to control serialization/deserialization behavior per class or field.
Common Annotations
@JsonProperty
Rename JSON property
@JsonIgnore
Exclude field
@JsonInclude
Conditional inclusion
@JsonFormat
Format dates
@JsonAlias
Accept multiple names
@JsonCreator
Constructor/factory-based deserialization
@JsonValue
Serialize enum or custom object as a value
Examples
public class Product {
@JsonProperty("item_name")
private String name;
@JsonIgnore
private String internalCode;
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate releaseDate;
@JsonAlias({"cost", "price_value"})
private double price;
}
Use Case
Maintain compatibility with legacy JSON.
Adjust naming or formatting without changing the class model.
Control visibility per API contract.
Working with JSON Tree (JsonNode)
Use JsonNode
when working with dynamic, partially known, or schema-less JSON structures.
Parsing JSON as Tree
String json = "{\"name\":\"Alice\", \"age\":30}";
JsonNode root = objectMapper.readTree(json);
String name = root.get("name").asText(); // "Alice"
int age = root.get("age").asInt(); // 30
Modifying Tree
((ObjectNode) root).put("name", "Bob");
Traversing Nested Nodes
JsonNode address = root.path("address");
String city = address.path("city").asText();
Creating a Tree Manually
ObjectNode node = objectMapper.createObjectNode();
node.put("username", "john");
node.put("active", true);
Use Case
Perfect for generic filters, form submissions, or when schema is not static.
Exception Handling
Jackson can throw several checked and runtime exceptions during JSON processing. It’s essential to handle these gracefully, especially in REST APIs.
Common Exceptions
UnrecognizedPropertyException
JSON has fields not found in the POJO
MismatchedInputException
Type mismatch (e.g., expecting object but got array)
JsonMappingException
Problems mapping between JSON and object
JsonParseException
Malformed JSON syntax
JsonProcessingException
Parent class for all exceptions
Spring REST Exception Handler
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(JsonProcessingException.class)
public ResponseEntity<String> handleJsonException(JsonProcessingException e) {
return ResponseEntity.badRequest().body("Invalid JSON: " + e.getOriginalMessage());
}
}
Custom Serializers and Deserializers
In most applications, Jackson’s default serialization and deserialization behavior is sufficient. However, certain situations call for custom logic, such as:
Encrypting/masking sensitive data
Transforming legacy formats
Applying business rules during serialization or deserialization
Interacting with non-standard or inconsistent APIs
Jackson makes this easy with JsonSerializer<T>
and JsonDeserializer<T>
interfaces.
1. Custom Serializer
Goal: Control how a Java object or field is written to JSON.
Steps:
a) Create a serializer class:
public class MaskingSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeString("***"); // Replace actual value with ***
}
}
b) Use it in our model:
public class User {
private String name;
@JsonSerialize(using = MaskingSerializer.class)
private String ssn; // Sensitive data
}
Output:
{
"name": "John",
"ssn": "***"
}
2. Custom Deserializer
Goal: Control how JSON is parsed into a Java object or field.
Steps:
a) Create a deserializer class:
public class UppercaseDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
return p.getText().toUpperCase();
}
}
b) Use it in our model:
public class Product {
private String code;
@JsonDeserialize(using = UppercaseDeserializer.class)
private String category;
}
Input:
{
"code": "PRD-101",
"category": "electronics"
}
Output:
product.getCategory(); // "ELECTRONICS"
3. Apply to Entire Class
We can serialize/deserialize an entire class with custom logic if we need control beyond a field level.
Example: Serialize full object to flat string
public class Coordinates {
public double lat;
public double lng;
}
Custom Serializer:
public class CoordinatesSerializer extends JsonSerializer<Coordinates> {
@Override
public void serialize(Coordinates value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeString(value.lat + "," + value.lng);
}
}
Use:
@JsonSerialize(using = CoordinatesSerializer.class)
public class Coordinates { ... }
Output:
"12.9716,77.5946"
4. Global Registration
Instead of annotating each model field or class, we can register custom (de)serializers globally.
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(String.class, new MaskingSerializer());
module.addDeserializer(String.class, new UppercaseDeserializer());
mapper.registerModule(module);
return mapper;
}
}
This affects every
String
field across the application. Be cautious with global registration.
5. Use with Spring Boot's Default ObjectMapper
If we don’t want to override Spring Boot’s default ObjectMapper
but want to add custom behavior, use a Jackson2ObjectMapperBuilderCustomizer
:
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> {
SimpleModule module = new SimpleModule();
module.addSerializer(String.class, new MaskingSerializer());
builder.modules(module);
};
}
This allows customization without replacing the default Spring Boot configuration.
Last updated
Was this helpful?