How to Parse Nested JSON in Java — Jackson, Gson, and org.json Guide
Parsing nested JSON in Java is straightforward once you know which library to use. Jackson is the industry standard for production apps. Gson works well for Android. org.json is simple for quick scripts. This guide covers all three with nested examples, error handling, and performance tips.
Jackson
most widely used JSON library in Java
Gson
Google's JSON library, great for Android
POJO mapping
best approach for complex nested structures
JsonNode
Jackson's tree model for dynamic JSON navigation
Nested JSON Example Used Throughout This Guide
We will use this nested JSON structure for all examples. It contains objects, arrays, deeply nested objects, and multiple levels of nesting — covering the most common patterns you will encounter in real APIs.
{
"user": {
"id": 123,
"name": "Alice Johnson",
"email": "alice@example.com",
"address": {
"street": "123 Main St",
"city": "Boston",
"country": "USA"
},
"orders": [
{
"orderId": "ORD-001",
"total": 99.99,
"items": [
{"name": "Laptop", "qty": 1},
{"name": "Mouse", "qty": 2}
]
}
]
}
}Jackson — POJO Mapping (Recommended for Production)
Jackson's POJO (Plain Old Java Object) mapping is the cleanest approach for deeply nested JSON. Define classes that mirror the JSON structure, and Jackson handles all the parsing with a single call to readValue(). This approach gives you compile-time type safety and works with any depth of nesting.
// Maven dependency:
// <dependency><groupId>com.fasterxml.jackson.core</groupId>
// <artifactId>jackson-databind</artifactId><version>2.17.0</version></dependency>
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
// Define POJOs matching JSON structure
public class Address {
public String street, city, country;
}
public class Item {
public String name;
public int qty;
}
public class Order {
public String orderId;
public double total;
public List<Item> items;
}
public class User {
public int id;
public String name, email;
public Address address;
public List<Order> orders;
}
public class Root {
public User user;
}
// Parse — one line for any depth of nesting
ObjectMapper mapper = new ObjectMapper();
Root root = mapper.readValue(jsonString, Root.class);
// Access deeply nested data
System.out.println(root.user.name); // Alice Johnson
System.out.println(root.user.address.city); // Boston
System.out.println(root.user.orders.get(0).orderId); // ORD-001
System.out.println(root.user.orders.get(0).items.get(0).name); // Laptop
// Read from file instead of string
Root rootFromFile = mapper.readValue(new File("data.json"), Root.class);Jackson — JsonNode (Dynamic Navigation)
When the JSON structure is unknown, dynamic, or you only need a few fields, Jackson's JsonNode tree model is better than defining POJOs. It lets you navigate JSON like a tree using path expressions, without any upfront class definitions.
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(jsonString);
// Navigate nested structure
String name = root.path("user").path("name").asText(); // Alice Johnson
String city = root.path("user").path("address").path("city").asText(); // Boston
// Access array elements
JsonNode orders = root.path("user").path("orders");
String orderId = orders.get(0).path("orderId").asText(); // ORD-001
// Iterate array
for (JsonNode order : orders) {
System.out.println(order.path("orderId").asText());
JsonNode items = order.path("items");
for (JsonNode item : items) {
System.out.println(" " + item.path("name").asText()
+ " x" + item.path("qty").asInt());
}
}
// Safely handle missing fields (returns null node, not exception)
JsonNode missing = root.path("user").path("nonexistent"); // returns MissingNode
boolean isMissing = missing.isMissingNode(); // true — no NPE
// path() vs get() difference:
// path() — returns MissingNode if not found (safe)
// get() — returns null if not found (NPE risk if you call methods on null)
// Always prefer path() for safe navigationJackson — Reading from File and URL
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.net.URL;
import java.io.InputStream;
ObjectMapper mapper = new ObjectMapper();
// From String
Root fromString = mapper.readValue(jsonString, Root.class);
// From File
Root fromFile = mapper.readValue(new File("data.json"), Root.class);
// From URL (downloads and parses)
Root fromUrl = mapper.readValue(new URL("https://api.example.com/user/123"), Root.class);
// From InputStream (useful for classpath resources)
InputStream stream = getClass().getResourceAsStream("/test-data.json");
Root fromStream = mapper.readValue(stream, Root.class);
// Handle unknown properties gracefully
// By default, Jackson throws on unknown fields
// To ignore them:
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// Or per-class with annotation: @JsonIgnoreProperties(ignoreUnknown = true)Gson — Simple Nested Parsing
Gson is Google's JSON library and the preferred choice for Android development. It has a simpler API than Jackson for basic use cases and works well when you need a lightweight dependency without Spring Boot's auto-configuration.
// Maven: <dependency><groupId>com.google.code.gson</groupId>
// <artifactId>gson</artifactId><version>2.10.1</version></dependency>
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
Gson gson = new Gson();
// Option 1: Map to POJO (same structure as Jackson)
Root root = gson.fromJson(jsonString, Root.class);
System.out.println(root.user.address.city); // Boston
// Option 2: Dynamic navigation with JsonObject
JsonObject json = JsonParser.parseString(jsonString).getAsJsonObject();
String name = json.getAsJsonObject("user").get("name").getAsString();
String city = json.getAsJsonObject("user")
.getAsJsonObject("address")
.get("city").getAsString();
// Array access
JsonArray orders = json.getAsJsonObject("user").getAsJsonArray("orders");
String orderId = orders.get(0).getAsJsonObject().get("orderId").getAsString();
// Read from file
try (Reader reader = new FileReader("data.json")) {
Root rootFromFile = gson.fromJson(reader, Root.class);
}
// Serialize back to JSON
String jsonOutput = gson.toJson(root);
// Pretty printing:
Gson prettyGson = new GsonBuilder().setPrettyPrinting().create();
System.out.println(prettyGson.toJson(root));org.json — Simple Script Parsing
// Maven: <dependency><groupId>org.json</groupId>
// <artifactId>json</artifactId><version>20231013</version></dependency>
import org.json.JSONObject;
import org.json.JSONArray;
JSONObject json = new JSONObject(jsonString);
// Navigate nested objects
JSONObject user = json.getJSONObject("user");
String name = user.getString("name"); // Alice Johnson
int id = user.getInt("id"); // 123
JSONObject address = user.getJSONObject("address");
String city = address.getString("city"); // Boston
// Access arrays
JSONArray orders = user.getJSONArray("orders");
JSONObject firstOrder = orders.getJSONObject(0);
String orderId = firstOrder.getString("orderId"); // ORD-001
// Safely get optional fields (returns null instead of throwing)
String phone = user.optString("phone", "N/A"); // "N/A" if not present
int rating = user.optInt("rating", 0); // 0 if not present
// Iterate array
for (int i = 0; i < orders.length(); i++) {
System.out.println(orders.getJSONObject(i).getString("orderId"));
}Error Handling for JSON Parsing
Production code must handle malformed JSON, missing fields, and type mismatches gracefully. Here is how to handle exceptions properly for each library.
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// Safe parse with full error handling
public Optional<Root> parseUserJson(String json) {
if (json == null || json.isBlank()) {
log.warn("Empty JSON input");
return Optional.empty();
}
try {
return Optional.of(mapper.readValue(json, Root.class));
} catch (JsonProcessingException e) {
// Includes: invalid syntax, missing required fields, type mismatch
log.error("Failed to parse user JSON: {}", e.getMessage());
log.debug("Raw JSON that failed: {}", json);
return Optional.empty();
} catch (Exception e) {
log.error("Unexpected error parsing JSON", e);
return Optional.empty();
}
}
// Use in application:
parseUserJson(apiResponse)
.map(root -> root.user)
.ifPresentOrElse(
user -> processUser(user),
() -> handleParseFailure(apiResponse)
);Which Library to Choose?
Jackson
Best for: Spring Boot apps (auto-configured), REST APIs, complex nested structures. Fastest performance, most features. Industry default in Java backend. Use Jackson unless you have a specific reason not to.
Gson
Best for: Android development, simple projects, when you prefer Google's API style. Slightly simpler API than Jackson. Good for projects that need a lightweight JSON library without Spring.
org.json
Best for: quick scripts, learning JSON in Java, minimal dependencies. No POJO mapping — manual field access only. Not recommended for large production apps — too much boilerplate.
JSON-B (Jakarta)
Best for: Jakarta EE/Quarkus applications. Standard Java EE JSON binding API. Auto-configured in Quarkus and Jakarta EE apps. Growing ecosystem but less ubiquitous than Jackson.
Always use ObjectMapper as a singleton