Java JSON Serialization & Deserialization with Jackson: Complete Guide

Jackson is the de facto standard for JSON in Java and the library Spring Boot uses under the hood. Its central class, ObjectMapper, converts Java objects to JSON (serialization) and JSON back into Java objects (deserialization). This guide covers the full round-trip, the annotations you will actually use, how to handle Java 8 dates, and clear fixes for the exceptions every Jackson user eventually hits.

Inspecting Jackson's output? Format and validate it in your browser:

JSON Formatter → JSON Validator →

What is serialization and deserialization?

Serialization is the process of converting an in-memory object into a format that can be stored or sent across a network — here, a JSON string. Deserialization is the reverse: rebuilding the in-memory object from that stored or received representation.

Why this matters in Java specifically: a Java object lives on the heap as a graph of references that only the JVM understands. You cannot put that graph onto an HTTP connection, write it to a file, or store it in a cache as-is. Serialization flattens the object into JSON text that any system — a browser, another microservice, a database — can read. When the data comes back, deserialization turns it into a typed Java object you can call methods on again.

Common places a Java application serializes and deserializes JSON:

Java has a built-in java.io.Serializable mechanism that does something similar, but it produces an opaque binary format only other JVMs can read. JSON is human-readable and language-agnostic, which is why libraries like Jackson have become the standard for almost all modern Java applications.

What is Jackson?

Jackson is a high-performance JSON library for Java built around three modules: jackson-core (low-level streaming), jackson-annotations (the annotation set), and jackson-databind (the ObjectMapper that ties objects to JSON). Adding jackson-databind pulls in the other two transitively, so it is the only dependency most projects declare.

<!-- Maven -->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.17.1</version>
</dependency>
// Gradle (build.gradle)
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1'

If you are using Spring Boot, Jackson is already on the classpath via spring-boot-starter-web — you do not need to add it yourself, and a pre-configured ObjectMapper bean is available for injection.

The ObjectMapper is thread-safe once configured. Create one instance and reuse it across your application rather than constructing a new one per request — instantiation is relatively expensive.

How do you serialize a Java object to JSON?

Serialization turns a Java object into a JSON string. The core method is writeValueAsString(). Jackson reads your object's public getters (or fields) to build the JSON.

public class User {
    private long id;
    private String name;
    private String email;

    // constructors, getters, and setters omitted for brevity
}

ObjectMapper mapper = new ObjectMapper();
User user = new User(42, "Alice", "alice@example.com");

String json = mapper.writeValueAsString(user);
// {"id":42,"name":"Alice","email":"alice@example.com"}

For readable output — logs, fixtures, or files a human will inspect — use the pretty printer:

String pretty = mapper.writerWithDefaultPrettyPrinter()
                      .writeValueAsString(user);
// {
//   "id" : 42,
//   "name" : "Alice",
//   "email" : "alice@example.com"
// }

// Write straight to a file instead of a String
mapper.writeValue(new File("user.json"), user);

How do you deserialize JSON to a Java object?

Deserialization parses a JSON string into a typed Java object with readValue(). The target class needs a public no-argument constructor (Jackson uses it to instantiate the object, then fills in fields via setters or direct field access).

String json = "{\"id\":42,\"name\":\"Alice\",\"email\":\"alice@example.com\"}";

ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(json, User.class);

user.getName(); // "Alice"

Deserializing a JSON array into a List

Java erases generic types at runtime, so List.class cannot tell Jackson what the elements are — you would get a List<LinkedHashMap> instead of a List<User>. Use a TypeReference to preserve the element type:

String jsonArray = "[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"}]";

List<User> users = mapper.readValue(
    jsonArray,
    new TypeReference<List<User>>() {}
);

users.get(0).getName(); // "Alice"

Essential Jackson annotations

Annotations let you control the mapping between JSON fields and Java properties without writing custom code. These are the ones you will reach for most often.

AnnotationWhat it does
@JsonProperty("name")Maps a field to a different JSON key (e.g. Java userName ↔ JSON user_name)
@JsonIgnoreExcludes a property from both serialization and deserialization
@JsonInclude(NON_NULL)Omits properties that are null from the output JSON
@JsonFormatControls date/number formatting (pattern, timezone)
@JsonAliasAccepts multiple JSON key names when reading (useful for renamed API fields)
@JsonCreatorMarks a constructor or factory method Jackson should use to build the object
@JsonIgnoreProperties(ignoreUnknown = true)Ignores JSON fields with no matching property instead of failing
@JsonIgnoreProperties(ignoreUnknown = true)
public class Account {

    @JsonProperty("account_id")
    private long id;

    @JsonAlias({"fullName", "displayName"})
    private String name;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String nickname; // dropped from JSON when null

    @JsonIgnore
    private String passwordHash; // never serialized or read

    // getters and setters
}

Excluding a property: @JsonIgnore vs transient

Most objects hold fields that should never appear in JSON — a cached value, a derived helper, an internal token. There are two ways to keep one out of the output.

The idiomatic Jackson approach is @JsonIgnore. It excludes the property from both serialization and deserialization, and it works no matter how the property is accessed (field, getter, or setter):

@JsonIgnore
private String passwordHash; // never serialized or read back

You can also use Java's built-in transient keyword. It was designed for Java's native Serializable mechanism rather than for Jackson, so its effect here is narrower. Jackson honors transient on a field it would otherwise auto-detect — but a public getter takes precedence. If a getter exists for the property, Jackson still serializes it and you must use @JsonIgnore instead:

public class Session {
    public String userId;
    public transient String cacheKey; // skipped: transient field, no getter
}

// mapper.writeValueAsString(session)
// {"userId":"u-123"}   — cacheKey is omitted

Recommendation: use @JsonIgnore for anything you want out of the JSON — it is explicit and getter-proof. Reach for transient only when a field also genuinely needs to be excluded from Java's native object serialization.

How do you serialize Java 8 dates and records?

Out of the box, plain Jackson does not know how to handle java.time types like LocalDate and LocalDateTime — it throws InvalidDefinitionException saying "Java 8 date/time type ... not supported by default." The fix is the jackson-datatype-jsr310 module.

<!-- Maven -->
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
  <version>2.17.1</version>
</dependency>
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());

// Write dates as ISO 8601 strings ("2026-05-27") instead of numeric arrays
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

String json = mapper.writeValueAsString(event);
// {"title":"Launch","date":"2026-05-27"}

Spring Boot auto-registers JavaTimeModule, so LocalDate works without configuration there. For date-storage conventions across languages, see the JSON date format guide — ISO 8601 is the cross-platform standard Jackson emits by default once the module is registered.

Java records (Java 16+) are supported natively from Jackson 2.12 — no extra annotations or constructors required:

public record Point(int x, int y) {}

Point p = mapper.readValue("{\"x\":3,\"y\":7}", Point.class);
p.x(); // 3

Common Jackson exceptions and how to fix them

Most Jackson frustration comes from a handful of exceptions. Here is what each one means and the exact fix.

1. UnrecognizedPropertyException — unknown JSON field

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException is thrown when the JSON contains a field your class does not declare. By default Jackson treats unknown fields as an error. This is extremely common when consuming third-party APIs that add fields over time.

// Fix 1 — per class
@JsonIgnoreProperties(ignoreUnknown = true)
public class User { /* ... */ }

// Fix 2 — globally on the mapper
mapper.configure(
    DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

2. InvalidDefinitionException — no default constructor

The message reads "Cannot construct instance ... no Creators, like default constructor, exist." Jackson cannot instantiate a class that only has an argument-taking constructor. Either add a no-arg constructor, or tell Jackson to use the existing one with @JsonCreator.

public class User {
    private final long id;
    private final String name;

    @JsonCreator
    public User(@JsonProperty("id") long id,
                @JsonProperty("name") String name) {
        this.id = id;
        this.name = name;
    }
    // getters
}

3. InvalidDefinitionException — Java 8 date/time not supported

"Java 8 date/time type java.time.LocalDate not supported by default" means you forgot to register the JSR-310 module. Register JavaTimeModule as shown in the dates section above.

4. InvalidFormatException — type mismatch

InvalidFormatException fires when a JSON value cannot be coerced into the target type — for example the JSON has "age": "thirty" but the field is an int. The fix is on the data side: correct the JSON, or change the field type to String if the value is genuinely textual. Validate the payload first with the JSON Validator when a malformed response is the suspected cause.

5. Infinite recursion / StackOverflowError on bidirectional relationships

When two entities reference each other (an Order has a Customer, and the Customer has a List<Order>), Jackson loops forever and throws StackOverflowError. Break the cycle with managed/back references, or annotate one side with @JsonIgnore.

public class Customer {
    @JsonManagedReference
    private List<Order> orders; // serialized normally
}

public class Order {
    @JsonBackReference
    private Customer customer; // omitted during serialization to break the loop
}

Frequently Asked Questions

What is the difference between serialization and deserialization in Jackson?

Serialization converts a Java object into a JSON string — in Jackson, objectMapper.writeValueAsString(obj). Deserialization is the reverse: it parses a JSON string into a Java object — objectMapper.readValue(json, Pojo.class). Both are handled by the same ObjectMapper instance. Serialization reads your object's getters (or fields); deserialization writes into the object via its no-argument constructor and setters, or via a constructor annotated with @JsonCreator.

Why does Jackson throw UnrecognizedPropertyException?

UnrecognizedPropertyException means the JSON contains a field that does not map to any property on your Java class, and Jackson is configured to fail on unknown fields by default. Fix it by adding @JsonIgnoreProperties(ignoreUnknown = true) to the class, or by disabling the feature globally: objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false). This is common when consuming third-party APIs that add new fields over time.

Why does Jackson need a no-argument constructor?

By default Jackson creates an instance with the no-argument constructor and then populates fields via setters or direct field access. If your class only has a constructor that takes arguments, Jackson cannot instantiate it and throws InvalidDefinitionException ("no Creators, like default constructor, exist"). Fix it by adding a no-arg constructor, or annotate your argument constructor with @JsonCreator and mark each parameter with @JsonProperty. Java records are handled natively from Jackson 2.12 without extra annotations.

How do I serialize Java 8 LocalDate and LocalDateTime with Jackson?

Add the jackson-datatype-jsr310 dependency and register its module: objectMapper.registerModule(new JavaTimeModule()). Then disable timestamp output so dates serialize as ISO 8601 strings: objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS). Without the module, Jackson throws InvalidDefinitionException saying Java 8 date/time is not supported by default. In Spring Boot the module is auto-registered, so LocalDate works out of the box.

How do I deserialize a JSON array into a List in Jackson?

Generic types like List<User> are erased at runtime, so passing List.class loses the element type. Use a TypeReference to preserve it: objectMapper.readValue(json, new TypeReference<List<User>>(){}). Alternatively, build a CollectionType with the type factory. Either approach tells Jackson to deserialize each array element into a User instead of a generic LinkedHashMap.

Format and validate the JSON your Jackson code produces

Paste serialized output to pretty-print it, or validate a payload before readValue — entirely in your browser, no data sent to a server.

JSON Formatter JSON Validator JSON to TypeScript

Need to inspect a Jackson payload right now? Pretty-print it in the browser.

Open JSON Formatter →
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds developer tools at JSON Dev Tools.