JSON to Java Class Converter
Generate Java classes with getters, or Java 16 records, from a JSON sample — with nested types, List generics and reserved-word handling. All processing happens locally in your browser.
Turn a JSON sample into Java classes or records
Fields are merged across every element of an array, so a key that appears in only some elements is still generated and boxed so it can be null. Reserved words and awkward keys are renamed to valid identifiers.
import com.fasterxml.jackson.annotation.JsonProperty;
public class Root {
private int id;
@JsonProperty("user_name")
private String userName;
private String email;
@JsonProperty("is_active")
private boolean isActive;
private double score;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public boolean isIsActive() {
return isActive;
}
public void setIsActive(boolean isActive) {
this.isActive = isActive;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
}Bind the document to Root.
What is generated: classes or records
Java binds JSON to objects through a library — Jackson in most Spring projects, Gson elsewhere. Either way you need a type whose fields match the payload. This page generates that type in two shapes, and the choice is not cosmetic.
Classes with getters and setters
public class User {
private int id;
@JsonProperty("user_name")
private String userName;
private boolean isActive;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
...
}
Mutable, works with every JSON library and every Java version, and satisfies the JavaBeans convention that Jackson and Gson both rely on by default. Verbose, but universally compatible.
Records
public record User(
int id,
String userName,
boolean isActive
) {}
Immutable, and roughly a fifth of the code. Available from Java 16, and supported by Jackson 2.12 and later. Records give you a canonical constructor, accessors, equals, hashCode and toString for free.
The trade-off: because a record is immutable and its constructor requires every component, a payload missing a field will bind that component to null (or fail, depending on configuration). Records suit DTOs you read once; classes suit objects you build up incrementally.
How to generate types
- Paste a representative JSON sample Every element of an array is inspected, so a field present in only some records is still found.
- Pick classes or records Records need Java 16 or later and Jackson 2.12 or later.
-
Choose numeric types
int,longorBigIntegerfor whole numbers;doubleorBigDecimalfor decimals. Values beyondintrange are promoted tolongautomatically. - Review the notes and split the files Java requires one public type per file. The output shows all generated types together for review.
Java identifier rules the generator has to work around
Java has stricter naming constraints than most languages JSON gets mapped to, and real payloads violate them regularly.
| JSON key | Java field | Problem solved |
|---|---|---|
user_name |
userName |
Underscores are not conventional in Java field names |
class |
classValue |
class is a reserved word and will not compile |
public |
publicValue |
Same — Java has 50+ reserved words |
2fa_enabled |
fieldFaEnabled |
An identifier cannot start with a digit |
request-id |
requestID |
A hyphen is not valid in an identifier |
XMLPayload |
xmlPayload |
Leading acronym lowercased for camelCase |
Every one of those renames breaks the automatic name-to-key mapping, which is exactly what @JsonProperty is for.
When @JsonProperty is actually needed
Jackson matches a JSON key to a field by name, so userName finds "userName" without help. It does not find "user_name". The annotation is only emitted when the names genuinely differ:
@JsonProperty("user_name")
private String userName; // needed
private String email; // not needed, names match
Annotating everything is harmless but adds noise, so it is skipped where it would do nothing. If your whole API is snake_case, a cleaner option is to configure the naming strategy once instead:
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class User { ... }
Examples: numeric types and why BigDecimal exists
JSON has one number type, so mapping it to Java's several is a decision rather than a detection.
| Sample | Default | Consider instead |
|---|---|---|
42 |
int |
long if IDs might exceed 2,147,483,647 |
9007199254740993 |
long (promoted) |
BigInteger beyond 64 bits |
91.5 |
double |
Fine for measurements |
19.99 as money |
double |
BigDecimal — always |
The money case is worth spelling out because it causes real financial bugs. double is binary floating point, so decimal fractions have no exact representation:
double a = 0.1 + 0.2;
System.out.println(a); // 0.30000000000000004
double total = 0;
for (int i = 0; i < 10; i++) total += 0.1;
System.out.println(total); // 0.9999999999999999
BigDecimal b = new BigDecimal("0.1").add(new BigDecimal("0.2"));
System.out.println(b); // 0.3
Note that BigDecimal must be constructed from a string. new BigDecimal(0.1) takes a double and inherits the same imprecision, which defeats the purpose. Jackson does this correctly when the target field is BigDecimal.
Primitives versus wrappers
An optional field is generated with a wrapper type — Integer rather than int — because a primitive cannot be null. Bind a missing JSON key to an int and you get 0, indistinguishable from a real zero. Bind it to Integer and you get null, which is the truth.
The same applies inside collections: Java generics cannot hold primitives, so a list of numbers is always List<Integer>, never List<int>.
Nested objects and collections
A nested object becomes its own type, named from its key:
{
"orderId": "A-1001",
"customer": {
"id": 12,
"address": { "line1": "1 Navy Way", "city": "Arlington" }
}
}
generates Root, Customer and Address. Identical shapes are deduplicated, so billingAddress and shippingAddress with the same keys share one type rather than producing two identical classes.
An array of objects becomes List<T> with a singularised element name — "results" produces List<Result>. An empty array has no observable element type, so it becomes List<Object> and is flagged.
One public type per file. Java requires a public type to live in a file named after it, so a five-type result needs five files. Alternatively, make the nested types static inner classes of the root type and keep everything in one file — reasonable for DTOs that are only used together.
Use cases
- Building a Spring Boot client. Paste a third-party response and get the DTO that
RestTemplateorWebClientwill bind to. - Writing a request body type. A sample of what clients post becomes your
@RequestBodyparameter type. - Consuming a Kafka message. Generate the value type for a deserialiser from one sample message.
- Typing a configuration file. A JSON config becomes a
@ConfigurationPropertiesclass. - Modernising legacy code. Replace
Map<String, Object>access with a real type and let the compiler find the field-name typos. - Checking an unfamiliar payload. The generated types plus the review notes act as a compact schema, showing which fields are inconsistent across records.
For other targets, JSON to Go generates structs from the same model. Java configuration often involves .properties files too — Properties to YAML and XML to Properties cover those. To validate the JSON first, use the JSON Schema Validator.
Privacy
Parsing and generation run in your browser; the JSON is not uploaded. Usage analytics record only the output style, the number of generated types and whether annotations were enabled — never the JSON or the generated code.
Frequently Asked Questions
Should I choose classes or records?
Records for read-only DTOs on Java 16+ with Jackson 2.12+ — they are immutable and far shorter. Classes when you need mutability, are on an older Java version, or use a framework that requires a no-argument constructor and setters, such as some JPA and older Spring configurations.
Why is @JsonProperty only on some fields?
Because Jackson matches names automatically when they are identical. An annotation on email for the key "email" does nothing. It is only emitted where the rename was unavoidable — snake_case keys, reserved words, keys starting with a digit, or keys containing hyphens.
Why is my optional field Integer rather than int?
A primitive cannot hold null, so a missing key would bind to 0 and be indistinguishable from a real zero. The wrapper type preserves the difference. This is the Java equivalent of the pointer types the Go generator uses for the same reason.
When should I use BigDecimal?
For any monetary or exact decimal value. double is binary floating point, so 0.1 + 0.2 is 0.30000000000000004 and repeated addition drifts. Construct BigDecimal from a string, never from a double — new BigDecimal(0.1) inherits the same imprecision it is meant to avoid.
Why do I get several types instead of one file?
Because each nested JSON object needs its own Java type, and Java requires one public type per file named after it. Split them into separate files, or convert the nested ones into static inner classes of the root type if they are only used together.
Does it generate Lombok annotations?
No. Lombok is a build-time dependency with its own annotation processor, so generating @Data would produce code that does not compile without it configured. Records give you most of what Lombok's @Value provides using only the standard language.
What happens to a JSON key called "class" or "public"?
It is renamed with a Value suffix — classValue, publicValue — because Java reserved words cannot be identifiers. A @JsonProperty annotation preserves the original key so binding still works against the real payload.