Example
{
"order_id": 5012,
"status": "paid",
"customer": {
"id": "c_88",
"email": "lin@example.com"
},
"total": 59.9,
"lines": [
{
"sku": "TSHIRT-M",
"qty": 2
},
{
"sku": "MUG",
"qty": 1,
"gift_note": "Happy birthday!"
}
]
}
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public record Order(
@JsonProperty("order_id") long orderId,
String status,
Customer customer,
double total,
List<Line> lines
) {
public record Customer(String id, String email) {}
public record Line(String sku, long qty, @JsonProperty("gift_note") String giftNote) {}
}
order_idbecameorderIdwith@JsonProperty("order_id"), so Jackson still maps it.gift_noteappears in only one line item, so it can be missing.- Identical object shapes share one type instead of being duplicated.
Parsing JSON with Jackson
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper mapper = new ObjectMapper();
// A single object
Order order = mapper.readValue(json, Order.class);
System.out.println(order.customer().email());
// A JSON array: generic types need a TypeReference
List<Order> orders = mapper.readValue(json, new TypeReference<List<Order>>() {});
// And back to JSON
String out = mapper.writeValueAsString(order);
With records, accessors have no get prefix: order.customer(), not
order.getCustomer().
Type mapping
| JSON | Java |
|---|---|
| string | String (optionally OffsetDateTime, LocalDate or UUID) |
| integer | long, or Long when nullable |
| decimal number | double, or Double when nullable |
| true / false | boolean, or Boolean when nullable |
| object | nested record or static class |
| array | List<T> |
| always null / mixed types | Object |
Integers map to long because a JSON sample doesn’t tell you the range, and IDs and timestamps often
exceed int’s 2,147,483,647. Change to int where you know values are small. For money, replace
double with BigDecimal to avoid rounding errors.
Dates and java.time
With java.time and UUID types enabled, ISO 8601 timestamps become OffsetDateTime and
dates become LocalDate. Jackson only understands these after registering the JSR-310 module:
// com.fasterxml.jackson.datatype:jackson-datatype-jsr310
ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());
Spring Boot registers it automatically. Generating for other languages? Try JSON to Kotlin, JSON to C# or JSON to Go.
Frequently asked questions
How do I convert JSON to a Java class?
Paste a JSON sample into the tool above. It creates a Java type for every object, picks the right Java types (String, long, double, boolean, List), converts keys to camelCase field names and adds @JsonProperty where the JSON key differs, so Jackson maps it correctly. Copy the result into a .java file named after the outer type.
Should I use records or classes?
Use records on Java 16 or newer: they are immutable, short, and supported by Jackson 2.12+ and Gson 2.10+. Choose Lombok if your project already uses it and you need mutable objects, or plain getters and setters for older Java versions and frameworks that require a no-argument constructor, such as some JPA setups.
Why are nested types inside the outer class?
A Java file can only contain one public top-level type, so the generator nests the others inside it. That way the result compiles as a single file. Nested records are implicitly static; nested classes are declared static so Jackson can create them. Move them to their own files if you prefer.
Why is a field Integer or Long instead of int or long?
Primitive types cannot be null. When a key is missing from some samples or is sometimes null, the generator uses the boxed type (Long, Double, Boolean) so the value can be null instead of silently becoming 0 or false.
Why does Jackson fail with “Unrecognized field”?
Jackson’s ObjectMapper rejects JSON keys that have no matching field by default. Add @JsonIgnoreProperties(ignoreUnknown = true) to the type, or configure mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false). Spring Boot already turns this check off.