W3docs

Java JSON with Jackson

Parse, generate, and bind JSON in Java with the Jackson library — ObjectMapper and data binding.

Java JSON with Jackson

Jackson is the de-facto standard JSON library for Java. The JDK ships no JSON API of its own, so almost every Spring, Quarkus, or Micronaut application reaches for Jackson to turn Java objects into JSON text and back again. Its entry point is a single workhorse class — ObjectMapper — that handles the two jobs you always need: serialization (Java object → JSON) and deserialization (JSON → Java object).

Adding Jackson to your project

Jackson is not part of the JDK, so you declare it as a dependency. The jackson-databind artifact pulls in the other two core modules (jackson-core and jackson-annotations) transitively.

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

The three ways to work with JSON

Jackson exposes the same data three different ways. Pick the level that fits the task:

ApproachCore typeUse it when
Data bindingObjectMapper.readValue / writeValueYou have a POJO or record that mirrors the JSON — the common case
Tree modelJsonNode via readTreeThe shape is dynamic or you only need a few fields
StreamingJsonParser / JsonGeneratorHuge documents where you cannot hold the whole tree in memory

Data binding is what you use 90% of the time; the other two are escape hatches.

Data binding: objects in, JSON out

ObjectMapper.writeValueAsString serializes any Java object by reading its getters (or record components); readValue does the reverse, matching JSON field names to your fields. A record is the cleanest target — Jackson 2.12+ binds to its canonical constructor automatically.

import com.fasterxml.jackson.databind.ObjectMapper;

record User(String name, int age, boolean active) {}

ObjectMapper mapper = new ObjectMapper();

// serialize: Java -> JSON
User ada = new User("Ada", 36, true);
String json = mapper.writeValueAsString(ada);
// {"name":"Ada","age":36,"active":true}

// deserialize: JSON -> Java
User back = mapper.readValue(json, User.class);
System.out.println(back.name()); // Ada

For collections and generics, hand Jackson a TypeReference so the element type survives erasure:

import com.fasterxml.jackson.core.type.TypeReference;

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

The tree model: when the shape is unknown

When you do not have (or want) a matching class, parse into a generic JsonNode tree and navigate it by key. This mirrors what a hand-rolled Map/List parser does, but with type-aware accessors like asInt() and asText().

import com.fasterxml.jackson.databind.JsonNode;

JsonNode root = mapper.readTree(json);
String name = root.get("name").asText();
int age     = root.get("age").asInt();
JsonNode first = root.get("languages").get(0); // array access by index

Controlling the mapping with annotations

JSON field names rarely match Java conventions perfectly. A handful of annotations close the gap without changing your field names:

AnnotationEffect
@JsonProperty("user_name")Map a field to a different JSON key
@JsonIgnoreOmit a field from both directions
@JsonInclude(NON_NULL)Drop null fields from the output
@JsonCreator / @JsonFormatCustom construction / date formatting
record Account(
    @JsonProperty("user_name") String userName,
    @JsonIgnore String passwordHash) {}

A worked example: serialize, parse, and bind by hand

Jackson is not on this runner's classpath, so the program below builds the same concepts — a JSON writer, a recursive-descent parser into a Map/List tree, and binding into a record — using only java.util. It is exactly what ObjectMapper does for you under the hood: walk an object graph to emit text, and tokenize text to rebuild a tree.

java— editable, runs on the server

What to take from the run:

  • Serialization walks an object graph and emits text. The serialized line shows a nested Map/List rendered to {"name":"Ada",...,"languages":["Java","Ada"]} — an array inside an object. ObjectMapper.writeValueAsString does the same recursive walk over your POJO's getters or a record's components.
  • Parsing rebuilds a generic tree first. The parsed value's runtime type is LinkedHashMap, not User — exactly Jackson's tree model, where readTree hands you a JsonNode you navigate by key before any class is involved.
  • JSON numbers become Java numbers, with a type decision. The age field came back as an Integer (42) because the text had no decimal point; the parser chose Integer over Double. Jackson makes the identical choice, which is why an int field binds cleanly while 3.14 would land as a Double.
  • Field access is by name, and order is preserved. Using LinkedHashMap kept the keys in insertion order, so name reads back before age. Jackson preserves object key order the same way, which is why round-tripped JSON looks like the original.
  • Round-tripping is lossless when the model matches. The final line re-serialized the parsed tree to the same JSON it came from, and the typed User record bound name, age, and languages correctly — the whole point of data binding: text in, object out, text back, no drift.

Practice

Practice

With Jackson, you receive a JSON response whose structure you do not control and you only need to read two fields out of a large, deeply nested document. Which approach fits best?