Jackson
Library
Jackson
Overview
Jackson is Java's most popular and high-performance JSON library. Adopted as the default library for Spring Boot, it supports JSON, XML, and YAML processing and provides enterprise-grade functionality. With rich annotations, flexible configuration options, and three approaches of Streaming API, Tree Model, and data binding for diverse usage scenarios, it has established itself as the standard choice for Java enterprise applications.
Details
Jackson 2.19.0 is a mature library whose excellent performance in large-scale file processing has been recognized through adoption in the Spring Boot ecosystem. Composed of three modules: jackson-core (streaming API), jackson-databind (data binding), and jackson-annotations (annotations), it provides comprehensive functionality required for enterprise development, including high-level API through ObjectMapper, Builder pattern support, and custom serializers/deserializers.
Key Features
- Comprehensive Format Support: Unified processing of JSON, XML, YAML, CSV, etc.
- High Performance: Low memory footprint through streaming API
- Rich Annotations: @JsonProperty, @JsonIgnore, @JsonCreator, etc.
- Flexible Configuration: Fine-grained control through SerializationFeature and DeserializationFeature
- Tree Model: DOM-style JSON manipulation through JsonNode
- Enterprise Grade: Deep integration with frameworks like Spring Boot
Pros and Cons
Pros
- High reliability through default adoption in major Java frameworks like Spring Boot
- Flexible customization through rich annotations and configuration options
- Three approaches: Streaming API, data binding, and Tree Model
- Excellent performance and low memory consumption for large-scale data processing
- Over 10 years of development track record and active community support
- Support for formats beyond JSON including XML, YAML, etc.
Cons
- High learning cost and complex configuration due to rich functionality
- May be overkill and over-specification for basic use cases
- Many dependencies potentially increasing application size
- Reduced code readability when annotations are overused
- Some advanced features depend on Java reflection affecting performance
- Runtime errors prone to occur due to configuration mistakes
Reference Pages
Code Examples
Basic Setup
<!-- Maven Dependencies -->
<properties>
<jackson.version>2.19.0</jackson.version>
</properties>
<dependencies>
<!-- Data binding (includes core and annotations) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- XML processing -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- YAML processing -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Java 8 time type support -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
// Gradle Dependencies
dependencies {
implementation "com.fasterxml.jackson.core:jackson-databind:2.19.0"
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.19.0"
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.19.0"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.19.0"
}
Basic Serialization and Deserialization
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.List;
public class JacksonBasics {
// ObjectMapper creation (recommended to create once and reuse)
private static final ObjectMapper mapper = new ObjectMapper();
// Basic POJO
public static class User {
public String name;
public int age;
public String email;
public boolean active;
// Default constructor (required)
public User() {}
public User(String name, int age, String email, boolean active) {
this.name = name;
this.age = age;
this.email = email;
this.active = active;
}
// getter/setter (optional - if using public fields)
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public boolean isActive() { return active; }
public void setActive(boolean active) { this.active = active; }
}
public static void basicOperations() throws IOException {
// Object creation
User user = new User("John Doe", 30, "[email protected]", true);
// 1. Object → JSON string
String jsonString = mapper.writeValueAsString(user);
System.out.println("JSON string: " + jsonString);
// 2. Object → JSON file
mapper.writeValue(new File("user.json"), user);
// 3. Object → byte array
byte[] jsonBytes = mapper.writeValueAsBytes(user);
System.out.println("JSON byte count: " + jsonBytes.length);
// 4. JSON string → Object
String jsonInput = "{\"name\":\"Jane Smith\",\"age\":25,\"email\":\"[email protected]\",\"active\":true}";
User deserializedUser = mapper.readValue(jsonInput, User.class);
System.out.println("Deserialized: " + deserializedUser.getName());
// 5. File → Object
User userFromFile = mapper.readValue(new File("user.json"), User.class);
System.out.println("From file: " + userFromFile.getName());
// 6. URL → Object (REST API call)
// User userFromUrl = mapper.readValue(new URL("https://api.example.com/user/123"), User.class);
}
// Generic type processing
public static void genericTypes() throws IOException {
// Map type
Map<String, Integer> scoreMap = Map.of("English", 85, "Math", 92, "Science", 78);
String mapJson = mapper.writeValueAsString(scoreMap);
Map<String, Integer> deserializedMap = mapper.readValue(mapJson,
new TypeReference<Map<String, Integer>>() {});
System.out.println("Map: " + deserializedMap);
// List type
List<User> users = List.of(
new User("John", 30, "[email protected]", true),
new User("Jane", 25, "[email protected]", false)
);
String listJson = mapper.writeValueAsString(users);
List<User> deserializedUsers = mapper.readValue(listJson,
new TypeReference<List<User>>() {});
System.out.println("User count: " + deserializedUsers.size());
// Complex nested types
Map<String, List<User>> departmentUsers = Map.of(
"Engineering", List.of(new User("Dev Smith", 28, "[email protected]", true)),
"Sales", List.of(new User("Sales Johnson", 32, "[email protected]", true))
);
String complexJson = mapper.writeValueAsString(departmentUsers);
Map<String, List<User>> deserializedComplex = mapper.readValue(complexJson,
new TypeReference<Map<String, List<User>>>() {});
System.out.println("Department count: " + deserializedComplex.size());
}
}
Annotations and Customization
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import java.time.LocalDateTime;
import java.util.List;
// Advanced POJO using annotations
@JsonIgnoreProperties(ignoreUnknown = true) // Ignore unknown properties
public class AdvancedUser {
@JsonProperty("user_id") // Specify JSON property name
private Long id;
@JsonProperty("full_name")
private String name;
@JsonAlias({"email_address", "mail"}) // Support multiple aliases
private String email;
@JsonIgnore // Ignore in serialization/deserialization
private String password;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") // Date format specification
private LocalDateTime createdAt;
@JsonInclude(JsonInclude.Include.NON_NULL) // Exclude null values
private String description;
@JsonInclude(JsonInclude.Include.NON_EMPTY) // Exclude empty collections
private List<String> tags;
// Write-only property (deserialization only)
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String temporaryToken;
// Read-only property (serialization only)
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private String internalStatus;
// Custom constructor for deserialization
@JsonCreator
public AdvancedUser(
@JsonProperty("user_id") Long id,
@JsonProperty("full_name") String name,
@JsonProperty("email") String email) {
this.id = id;
this.name = name;
this.email = email;
this.createdAt = LocalDateTime.now();
}
// getter/setter
public Long getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public LocalDateTime getCreatedAt() { return createdAt; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public List<String> getTags() { return tags; }
public void setTags(List<String> tags) { this.tags = tags; }
public String getTemporaryToken() { return temporaryToken; }
public void setTemporaryToken(String temporaryToken) { this.temporaryToken = temporaryToken; }
public String getInternalStatus() { return internalStatus; }
public void setInternalStatus(String internalStatus) { this.internalStatus = internalStatus; }
}
// Immutable class using Builder pattern
@JsonDeserialize(builder = ImmutablePerson.Builder.class)
public class ImmutablePerson {
private final String name;
private final Integer age;
private final String email;
private ImmutablePerson(String name, Integer age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public String getName() { return name; }
public Integer getAge() { return age; }
public String getEmail() { return email; }
@JsonPOJOBuilder(buildMethodName = "create", withPrefix = "set")
public static class Builder {
@JsonProperty("known_as")
@JsonAlias({"identifier", "first_name"})
private String name;
private Integer age;
private String email;
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setAge(Integer age) {
this.age = age;
return this;
}
public Builder setEmail(String email) {
this.email = email;
return this;
}
public ImmutablePerson create() {
return new ImmutablePerson(name, age, email);
}
}
}
ObjectMapper Detailed Configuration
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class JacksonConfiguration {
public static ObjectMapper createConfiguredMapper() {
ObjectMapper mapper = new ObjectMapper();
// Serialization configuration
mapper.enable(SerializationFeature.INDENT_OUTPUT); // Pretty print
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // Output dates as strings
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); // Don't fail on empty beans
// Deserialization configuration
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); // Don't fail on unknown properties
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT); // Accept empty strings as null
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); // Accept single values as arrays
// Parser configuration
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true); // Allow comments
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); // Allow single quotes
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); // Allow unquoted field names
// Generator configuration
mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, false); // Don't escape non-ASCII characters
// Java 8 time type support
mapper.registerModule(new JavaTimeModule());
// Custom module registration
SimpleModule customModule = new SimpleModule();
customModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
customModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer());
mapper.registerModule(customModule);
return mapper;
}
// Custom serializer
public static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeString(value.format(formatter));
}
}
// Custom deserializer
public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
return LocalDateTime.parse(p.getValueAsString(), formatter);
}
}
// Configuration usage example
public static void demonstrateConfiguration() throws IOException {
ObjectMapper mapper = createConfiguredMapper();
// Using the configured mapper
AdvancedUser user = new AdvancedUser(1L, "Configuration Test", "[email protected]");
user.setDescription(""); // Empty string
user.setTags(List.of("tag1", "tag2"));
String json = mapper.writeValueAsString(user);
System.out.println("Configured JSON:\n" + json);
// Parsing flexible JSON (with comments, single quotes, etc.)
String flexibleJson = """
{
// This is a comment
'user_id': 2,
'full_name': 'Flexible Parser',
email: '[email protected]', // Unquoted field name
unknown_field: 'This will be ignored'
}
""";
AdvancedUser parsed = mapper.readValue(flexibleJson, AdvancedUser.class);
System.out.println("Flexible parsing result: " + parsed.getName());
}
}
Tree Model (JsonNode) for Dynamic JSON Processing
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
public class JacksonTreeModel {
private static final ObjectMapper mapper = new ObjectMapper();
public static void treeModelOperations() throws IOException {
// Create JsonNode from JSON string
String jsonString = """
{
"user": {
"name": "John Doe",
"age": 30,
"contact": {
"email": "[email protected]",
"phone": "555-1234"
},
"hobbies": ["reading", "movies", "programming"]
},
"metadata": {
"created": "2025-01-01",
"version": "1.0"
}
}
""";
JsonNode root = mapper.readTree(jsonString);
// Read values
String name = root.get("user").get("name").asText();
int age = root.get("user").get("age").asInt();
String email = root.get("user").get("contact").get("email").asText();
System.out.println("Name: " + name + ", Age: " + age + ", Email: " + email);
// Value access using deep paths (JSON Pointer)
String phone = root.at("/user/contact/phone").asText();
String version = root.at("/metadata/version").asText();
System.out.println("Phone: " + phone + ", Version: " + version);
// Array processing
JsonNode hobbies = root.get("user").get("hobbies");
if (hobbies.isArray()) {
System.out.println("Hobbies:");
for (JsonNode hobby : hobbies) {
System.out.println(" - " + hobby.asText());
}
}
// Field existence check
if (root.has("user") && root.get("user").has("contact")) {
System.out.println("Contact information exists");
}
// Null check
JsonNode description = root.get("user").get("description");
if (description == null || description.isNull()) {
System.out.println("Description field doesn't exist or is null");
}
}
public static void modifyJsonTree() throws IOException {
// Create new JSON object
ObjectNode root = mapper.createObjectNode();
// Set basic values
root.put("name", "New User");
root.put("age", 25);
root.put("active", true);
root.put("score", 95.5);
// Create nested object
ObjectNode contact = root.putObject("contact");
contact.put("email", "[email protected]");
contact.put("phone", "555-9876");
// Create array
ArrayNode skills = root.putArray("skills");
skills.add("Java");
skills.add("Spring Boot");
skills.add("JSON");
// Add existing object
User existingUser = new User("Existing User", 35, "[email protected]", true);
root.putPOJO("existing_user", existingUser);
// Dynamically create nested object
root.withObject("deep").withObject("nested").withObject("structure").put("value", 42);
// Create using JSON Pointer
root.withObject("/settings/ui").put("theme", "dark");
root.withObject("/settings/notifications").put("email", true);
// Output result as JSON string
String result = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root);
System.out.println("Created JSON:\n" + result);
// Modify some fields
root.put("age", 26); // Update age
root.get("contact").put("email", "[email protected]"); // Update email
// Add element to array
((ArrayNode) root.get("skills")).add("Jackson");
System.out.println("\nUpdated JSON:\n" +
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root));
}
// Mixed use of Tree Model and POJO
public static void mixedApproach() throws IOException {
// Partially structured JSON data
String mixedJson = """
{
"user": {
"name": "Mixed User",
"age": 28,
"email": "[email protected]"
},
"dynamic_metadata": {
"custom_field1": "value1",
"custom_field2": 123,
"nested": {
"some_flag": true
}
},
"known_settings": {
"theme": "light",
"language": "en"
}
}
""";
JsonNode root = mapper.readTree(mixedJson);
// Convert known structure to POJO
User user = mapper.treeToValue(root.get("user"), User.class);
System.out.println("User: " + user.getName());
// Convert dynamic part to Map
Map<String, Object> dynamicMetadata = mapper.treeToValue(
root.get("dynamic_metadata"), Map.class);
System.out.println("Dynamic metadata: " + dynamicMetadata);
// Get specific values directly
String theme = root.get("known_settings").get("theme").asText();
boolean someFlag = root.at("/dynamic_metadata/nested/some_flag").asBoolean();
System.out.println("Theme: " + theme + ", Flag: " + someFlag);
// Assemble result as new JSON
ObjectNode response = mapper.createObjectNode();
response.putPOJO("processed_user", user);
response.putPOJO("metadata_summary", Map.of(
"field_count", dynamicMetadata.size(),
"theme", theme,
"flag", someFlag
));
String responseJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(response);
System.out.println("Processing result:\n" + responseJson);
}
}
Streaming API for High-Performance Processing
import com.fasterxml.jackson.core.*;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class JacksonStreaming {
private static final ObjectMapper mapper = new ObjectMapper();
// Streaming write for large JSON files
public static void streamingWrite() throws IOException {
File outputFile = new File("large_data.json");
try (JsonGenerator generator = mapper.createGenerator(outputFile, JsonEncoding.UTF8)) {
// Start large array
generator.writeStartArray();
// Write 1 million records incrementally
for (int i = 0; i < 1_000_000; i++) {
generator.writeStartObject();
generator.writeNumberField("id", i);
generator.writeStringField("name", "User " + i);
generator.writeStringField("email", "user" + i + "@example.com");
generator.writeBooleanField("active", i % 10 != 0);
generator.writeNumberField("score", i * 0.1);
// Nested object
generator.writeObjectFieldStart("metadata");
generator.writeStringField("department", "Dept " + (i % 5));
generator.writeNumberField("level", i % 3 + 1);
generator.writeEndObject();
generator.writeEndObject();
// Progress display (every 100,000 records)
if (i % 100_000 == 0) {
System.out.println("Written: " + i + " records");
}
}
generator.writeEndArray();
}
System.out.println("Streaming write completed");
}
// Streaming read for large JSON files
public static void streamingRead() throws IOException {
File inputFile = new File("large_data.json");
if (!inputFile.exists()) {
System.out.println("Input file doesn't exist. Please run streamingWrite() first.");
return;
}
int count = 0;
int activeCount = 0;
double totalScore = 0.0;
try (JsonParser parser = mapper.createParser(inputFile)) {
// Verify array start
JsonToken token = parser.nextToken();
if (token != JsonToken.START_ARRAY) {
throw new IOException("Expected array format JSON but got different format");
}
// Process each object sequentially
while (parser.nextToken() == JsonToken.START_OBJECT) {
boolean active = false;
double score = 0.0;
// Process fields within object sequentially
while (parser.nextToken() != JsonToken.END_OBJECT) {
String fieldName = parser.getCurrentName();
parser.nextToken(); // Move to value
switch (fieldName) {
case "active":
active = parser.getBooleanValue();
break;
case "score":
score = parser.getDoubleValue();
break;
case "metadata":
// Skip nested object
parser.skipChildren();
break;
default:
// Skip other fields
parser.skipChildren();
break;
}
}
// Update statistics
count++;
if (active) {
activeCount++;
}
totalScore += score;
// Progress display (every 100,000 records)
if (count % 100_000 == 0) {
System.out.println("Processed: " + count + " records");
}
}
}
System.out.println("Streaming read completed");
System.out.println("Total records: " + count);
System.out.println("Active users: " + activeCount);
System.out.println("Average score: " + (totalScore / count));
}
// Selective data extraction
public static List<User> selectiveExtraction(File jsonFile, int minAge) throws IOException {
List<User> result = new ArrayList<>();
try (JsonParser parser = mapper.createParser(jsonFile)) {
if (parser.nextToken() != JsonToken.START_ARRAY) {
throw new IOException("Expected array format JSON");
}
while (parser.nextToken() == JsonToken.START_OBJECT) {
String name = null;
int age = 0;
String email = null;
boolean active = false;
while (parser.nextToken() != JsonToken.END_OBJECT) {
String fieldName = parser.getCurrentName();
parser.nextToken();
switch (fieldName) {
case "name":
name = parser.getValueAsString();
break;
case "age":
age = parser.getIntValue();
break;
case "email":
email = parser.getValueAsString();
break;
case "active":
active = parser.getBooleanValue();
break;
default:
parser.skipChildren();
break;
}
}
// Add to list only if meets criteria
if (age >= minAge && active) {
result.add(new User(name, age, email, active));
}
}
}
return result;
}
public static void demonstrateStreaming() throws IOException {
System.out.println("=== Streaming API Demo ===");
// Large data write
System.out.println("1. Starting large data write...");
long startTime = System.currentTimeMillis();
streamingWrite();
long writeTime = System.currentTimeMillis() - startTime;
System.out.println("Write time: " + writeTime + "ms");
// Large data read
System.out.println("\n2. Starting large data read...");
startTime = System.currentTimeMillis();
streamingRead();
long readTime = System.currentTimeMillis() - startTime;
System.out.println("Read time: " + readTime + "ms");
// File size check
File file = new File("large_data.json");
System.out.println("File size: " + (file.length() / 1024 / 1024) + " MB");
}
}