Jackson

SerializationJavaJSONXMLYAMLData BindingHigh Performance

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");
    }
}