Lesson 36 • Advanced
JSON & XML Processing
By the end of this lesson you'll be able to turn Java objects into JSON and back with Jackson, walk dynamic JSON with the tree model, and safely read XML — the two data formats that power nearly every API and config file you'll ever touch.
What You'll Learn
- ✓Convert Java objects to JSON with Jackson's writeValueAsString
- ✓Parse JSON into objects with ObjectMapper.readValue
- ✓Rename and hide fields using @JsonProperty and @JsonIgnore
- ✓Navigate dynamic JSON with the JsonNode tree model
- ✓Know when to reach for Gson instead of Jackson
- ✓Read XML safely with DOM, and know DOM vs SAX vs JAXB
Before You Start
You should be comfortable with REST APIs (JSON is the standard API format) and Annotations (Jackson uses @JsonProperty, @JsonIgnore, and friends). A basic grasp of maps and lists helps too.
1️⃣ What Are JSON and XML? (Plain English)
JSON (JavaScript Object Notation) and XML (eXtensible Markup Language) are both ways to write data as plain text so two programs can exchange it. Your Java program can't send a live object across the internet, so it serializes the object to text, sends the text, and the other side deserializes it back into an object.
💡 Analogy: Think of flat-pack furniture. The object in memory is the assembled chair. To ship it, you flatten it into a labelled box (serialize to JSON/XML). At the other end someone reads the labels and rebuilds the chair (deserialize). JSON is the lean, modern box; XML is the older box with more packaging and stricter labels.
Here's the same data in both formats so you can feel the difference:
JSON
{
"name": "Alice",
"age": 30,
"active": true
}XML
<user> <name>Alice</name> <age>30</age> <active>true</active> </user>
| Feature | JSON | XML |
|---|---|---|
| Syntax | Lightweight, key-value | Verbose, tag-based |
| Data types | String, number, bool, array | Everything is text |
| Best for | REST APIs, configs, NoSQL | Enterprise, SOAP, documents |
| Java library | Jackson, Gson | JAXB, DOM, SAX, StAX |
For a brand-new project JSON wins almost every time. XML still shows up in SOAP web services, Maven pom.xml files, and older enterprise systems — so it's worth knowing both.
2️⃣ Jackson ObjectMapper — JSON ↔ Objects
Jackson is the most popular JSON library for Java, and Spring Boot uses it automatically. The one class you need is ObjectMapper. It does two jobs: writeValueAsString(obj) turns an object into JSON text, and readValue(json, Type.class) turns JSON text back into an object.
You map JSON onto a POJO — a plain class with fields. Two annotations control the mapping: @JsonProperty("key") renames a field in the JSON (handy when the API uses snake_case but you want camelCase in Java), and @JsonIgnore keeps a field out of the JSON entirely (perfect for passwords).
Read every comment in this worked example, then run it:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
// A POJO = "Plain Old Java Object": a simple class with fields.
// Jackson reads/writes JSON straight onto these fields.
static class User {
public String name;
public int age;
// @JsonProperty renames the JSON key. The field stays camelCase in Java,
// the JSON shows "is_active" (snake_case, the common API style).
@JsonProperty("is_active") public boolean isActive;
// @JsonIgnore keeps a field OUT of the JSON (e.g. a secret/password).
@JsonIgnore public String password;
}
public static void main(String[] args) throws Exception {
// ObjectMapper is the one object that does all the conversion.
ObjectMapper mapper = new ObjectMapper();
// 1) Serialize: Java object -> JSON text (writeValueAsString)
User u = new User();
u.name = "Alice"; u.age = 30; u.isActive = true; u.password = "secret123";
String json = mapper.writeValueAsString(u);
System.out.println("Serialized: " + json); // password is omitted
// 2) Deserialize: JSON text -> Java object (readValue)
String input = "{\"name\":\"Bob\",\"age\":25,\"is_active\":false}";
User parsed = mapper.readValue(input, User.class);
System.out.println("name: " + parsed.name); // Bob
System.out.println("age: " + parsed.age); // 25
System.out.println("active: " + parsed.isActive); // false
}
}Serialized: {"name":"Alice","age":30,"is_active":true}
name: Bob
age: 25
active: falsecom.fasterxml.jackson.core:jackson-databind to your Maven/Gradle build, then run with your local JDK.🎯 Your Turn #1 — map a POJO
Fill in the three blanks (___): the annotation that renames inStock, the serialize method, and the deserialize method. The expected output is in the comments so you can self-check.
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
// 🎯 YOUR TURN — finish the POJO and the two conversions
static class Product {
public String name;
public double price;
// 1) The JSON uses the key "in_stock" but you want a Java field
// called inStock. Add the annotation that renames it.
___ public boolean inStock; // 👉 @JsonProperty("in_stock")
}
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Product p = new Product();
p.name = "Mug"; p.price = 9.99; p.inStock = true;
// 2) Serialize p to a JSON string (Object -> JSON)
String json = mapper.___(p); // 👉 writeValueAsString
System.out.println(json);
// 3) Deserialize back into a Product (JSON -> Object)
Product copy = mapper.___(json, Product.class); // 👉 readValue
System.out.println("Name: " + copy.name + ", stock: " + copy.inStock);
// ✅ Expected output:
// {"name":"Mug","price":9.99,"in_stock":true}
// Name: Mug, stock: true
}
}___ using the hint on its line, then run with jackson-databind on your JDK.3️⃣ The Tree Model — JSON Without POJOs
Sometimes you only need one or two values from a big response, and writing matching classes would be overkill. Use the tree model: mapper.readTree(json) parses the JSON into a JsonNode you can walk like a document.
mapper.readValue(json, Order.class) — POJO mapping (typed, compile-time safe)
mapper.readTree(json) — tree model (dynamic, no class needed)
node.get("field") — step into an object or array
node.asText() / asInt() / asDouble() — read a leaf value
A JsonNode that holds an array is iterable, so you can loop over it with a normal for loop.
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String[] args) throws Exception {
// When you don't want to write matching POJOs, navigate the JSON as a
// tree. Text block (Java 15+) keeps the JSON readable.
String orderJson = """
{
"id": "ORD-001",
"customer": { "name": "Alice", "tier": "Gold" },
"items": [
{ "product": "Laptop", "quantity": 1, "price": 1299.99 },
{ "product": "Hub", "quantity": 2, "price": 49.99 }
]
}
""";
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(orderJson); // parse into a tree
// .get("key") steps into an object; .asText()/.asInt()/.asDouble() read values.
System.out.println("Order: " + root.get("id").asText()); // ORD-001
System.out.println("Tier: " + root.get("customer").get("tier").asText()); // Gold
System.out.println("Items: " + root.get("items").size()); // 2
// A JsonNode array is iterable — loop it like a List.
double total = 0;
for (JsonNode item : root.get("items")) {
double line = item.get("quantity").asInt() * item.get("price").asDouble();
total += line;
}
System.out.printf("Total: $%.2f%n", total); // Total: $1399.97
}
}Order: ORD-001
Tier: Gold
Items: 2
Total: $1399.97jackson-databind to your build and run with a local JDK.🎯 Your Turn #2 — read from a tree
No POJO this time. Fill in the three blanks to parse the JSON, read the top-level city as text, and reach into the nested weather.temp as an int.
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String[] args) throws Exception {
// 🎯 YOUR TURN — read values out of the tree (no POJO)
String json = "{\"city\":\"Paris\",\"weather\":{\"temp\":21,\"sky\":\"clear\"}}";
ObjectMapper mapper = new ObjectMapper();
// 1) Parse the JSON text into a tree
JsonNode root = mapper.___(json); // 👉 readTree
// 2) Read the top-level "city" value as text
String city = root.get("city").___; // 👉 asText()
// 3) Step into "weather" then read "temp" as an int
int temp = root.get("weather").get("temp").___; // 👉 asInt()
System.out.println(city + ": " + temp + "C");
// ✅ Expected output:
// Paris: 21C
}
}___ using the hint on its line, then run with jackson-databind on your JDK.4️⃣ A Note on Gson
Gson is Google's JSON library and the main alternative to Jackson. The API is even smaller — no ObjectMapper to configure, just a Gson object:
Gson gson = new Gson(); String json = gson.toJson(user); // Object -> JSON User back = gson.fromJson(json, User.class); // JSON -> Object
Gson is great for small apps or quick scripts where you want zero setup. For anything bigger — especially Spring Boot, which wires up Jackson for you — prefer Jackson for its richer annotations, streaming parser, and ecosystem. Pick one per project and stay consistent.
5️⃣ Parsing XML — DOM, SAX, and JAXB
Java ships with several XML tools. The three you'll meet most:
- DOM — loads the whole document into a tree in memory. Easy to navigate and edit, but a huge file can run you out of memory.
- SAX (and its cousin StAX) — streams the document, firing events as it reads. Memory stays constant for any file size, but you can only move forwards and can't edit.
- JAXB — maps XML to/from Java objects with annotations (
@XmlRootElement), the XML equivalent of Jackson. AMarshallerwrites objects to XML; anUnmarshallerreads XML into objects.
jakarta.xml.bind:jakarta.xml.bind-api plus an implementation.import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.io.StringReader;
import org.xml.sax.InputSource;
public class Main {
public static void main(String[] args) throws Exception {
String xml = "<book><title>Java Basics</title><pages>320</pages></book>";
// DOM: loads the WHOLE document into a tree in memory. Easy to navigate,
// but heavy for big files. (SAX/StAX stream instead — see the notes.)
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// SECURITY: turn off external entities to block XXE attacks.
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setExpandEntityReferences(false);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(xml)));
Element rootEl = doc.getDocumentElement(); // <book>
String title = rootEl.getElementsByTagName("title").item(0).getTextContent();
String pages = rootEl.getElementsByTagName("pages").item(0).getTextContent();
System.out.println("Root tag: " + rootEl.getTagName()); // book
System.out.println("Title: " + title); // Java Basics
System.out.println("Pages: " + pages); // 320
}
}Root tag: book
Title: Java Basics
Pages: 320🧩 Mini-Challenge — average JSON scores
Support is faded now: the starter only has a comment outline. Parse the scores array with the tree model, sum and count the values, and print the average to one decimal place.
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String[] args) throws Exception {
// 🎯 MINI-CHALLENGE: average a JSON array of scores
// The JSON below holds a "scores" array of numbers.
// 1. Parse it with ObjectMapper.readTree(...)
// 2. Loop over root.get("scores") (a JsonNode is iterable)
// 3. Add up each value with .asInt() and count them
// 4. Print "Average: X.X" to one decimal place (%.1f)
//
// ✅ Expected (scores 90, 80, 70): Average: 80.0
String json = "{\"scores\":[90,80,70]}";
// your code here
}
}jackson-databind to your build and run with a local JDK.Common Errors (and the fix)
- ❌
UnrecognizedPropertyException: Unrecognized field "..."— the JSON has a key your POJO doesn't declare. Fix:mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)or annotate the class with@JsonIgnoreProperties(ignoreUnknown = true). - ❌
InvalidDefinitionException: Cannot construct instance ... no default constructor— Jackson needs a no-argument constructor to build the object before setting fields. Fix: add a public no-arg constructor, or use@JsonCreatorwith@JsonPropertyparameters. - ❌
InvalidFormatException: Cannot deserialize value of type LocalDate from String "..."— the date string doesn't match the expected format. Fix: registerJavaTimeModuleand annotate the field with@JsonFormat(pattern = "yyyy-MM-dd"). - ❌
SAXParseException / OutOfMemoryErroron big XML — DOM tried to load a giant file into memory. Fix: switch to SAX or StAX streaming for large documents. - ❌ XXE vulnerability — parser fetched a local file — you parsed untrusted XML with defaults. Fix:
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)and disable external entities before parsing.
Pro Tips
- 💡 Create one
ObjectMapperand reuse it — it's thread-safe and expensive to build. - 💡 Use
@JsonProperty("user_name")to bridge snake_case APIs and camelCase Java fields. - 💡 Reach for
readTreewhen the shape is unknown or you need just a couple of fields. - 💡 For GB-sized files use Jackson's streaming
JsonParser(or XML's SAX/StAX) — constant memory.
📋 Quick Reference
| Task | Call | Notes |
|---|---|---|
| Object → JSON | mapper.writeValueAsString(obj) | Serialize |
| JSON → Object | mapper.readValue(json, T.class) | Needs a no-arg constructor |
| JSON → tree | mapper.readTree(json) | No POJO needed |
| Rename field | @JsonProperty("key") | snake_case ↔ camelCase |
| Hide field | @JsonIgnore | Omit from JSON |
| Gson (alt) | gson.toJson / fromJson | Lighter library |
| XML → tree | builder.parse(source) | DOM (small files) |
| XML streaming | SAXParser / XMLStreamReader | Large files, read-only |
| XML ↔ Object | Marshaller / Unmarshaller | JAXB |
❓ Frequently Asked Questions
Should I use Jackson or Gson for JSON in Java?
Use Jackson for most projects — it is the industry standard, Spring Boot auto-configures it, and it has the richest annotation and streaming support. Gson (from Google) is a lighter alternative with a simpler API (new Gson().toJson(obj) / fromJson(json, T.class)); it is fine for small apps or when you want zero configuration. Both map POJOs to JSON; pick one per project and stay consistent.
Why does Jackson throw 'no default constructor' when deserializing?
Jackson creates the object first (calling a no-argument constructor) and then sets the fields. If your class only has a constructor that takes arguments, the default one disappears, so Jackson cannot instantiate it. Fix it by adding a public no-arg constructor, or annotate a constructor with @JsonCreator and its parameters with @JsonProperty so Jackson knows how to build the object.
What is the difference between DOM and SAX for XML?
DOM loads the entire XML document into a tree in memory, so you can navigate and edit it freely — but a large file can exhaust memory. SAX (and StAX) stream the document, firing events as they read, so memory stays constant no matter how big the file is — but you can only move forwards and cannot edit. Use DOM for small documents you need to traverse; use SAX/StAX for large files you only need to read once.
How do I stop Jackson crashing when the JSON has extra fields?
By default Jackson throws UnrecognizedPropertyException if the JSON contains a key your POJO does not declare. Disable that check with mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false), or annotate the class with @JsonIgnoreProperties(ignoreUnknown = true). This makes your code resilient when an API adds new fields later.
What is an XXE attack and how do I prevent it?
XXE (XML External Entity) is a vulnerability where malicious XML declares an external entity that makes your parser read local files or hit internal URLs. Prevent it by hardening the factory before parsing: set the feature 'http://apache.org/xml/features/disallow-doctype-decl' to true, and disable external general/parameter entities. Always do this for any XML that comes from outside your application.
🎉 Lesson Complete!
You can now move data in and out of Java with confidence: serialize and deserialize POJOs with Jackson's ObjectMapper, rename and hide fields with @JsonProperty / @JsonIgnore, walk dynamic JSON with the tree model, choose Gson when you want something lighter, and read XML safely while knowing DOM from SAX from JAXB.
Next up: Unit Testing — write reliable tests with JUnit 5 and Mockito.
Sign up for free to track which lessons you've completed and get learning reminders.