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:
| Approach | Core type | Use it when |
|---|---|---|
| Data binding | ObjectMapper.readValue / writeValue | You have a POJO or record that mirrors the JSON — the common case |
| Tree model | JsonNode via readTree | The shape is dynamic or you only need a few fields |
| Streaming | JsonParser / JsonGenerator | Huge 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()); // AdaFor 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 indexControlling the mapping with annotations
JSON field names rarely match Java conventions perfectly. A handful of annotations close the gap without changing your field names:
| Annotation | Effect |
|---|---|
@JsonProperty("user_name") | Map a field to a different JSON key |
@JsonIgnore | Omit a field from both directions |
@JsonInclude(NON_NULL) | Drop null fields from the output |
@JsonCreator / @JsonFormat | Custom 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.
What to take from the run:
- Serialization walks an object graph and emits text. The
serializedline shows a nestedMap/Listrendered to{"name":"Ada",...,"languages":["Java","Ada"]}— an array inside an object.ObjectMapper.writeValueAsStringdoes 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, notUser— exactly Jackson's tree model, wherereadTreehands you aJsonNodeyou navigate by key before any class is involved. - JSON numbers become Java numbers, with a type decision. The
agefield came back as anInteger(42) because the text had no decimal point; the parser choseIntegeroverDouble. Jackson makes the identical choice, which is why anintfield binds cleanly while3.14would land as aDouble. - Field access is by name, and order is preserved. Using
LinkedHashMapkept the keys in insertion order, sonamereads back beforeage. 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
Userrecord boundname,age, andlanguagescorrectly — the whole point of data binding: text in, object out, text back, no drift.
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?