I work in a polyglot shop. The data pipeline team uses Python, the backend services team uses Java, and I'm stuck in the middle translating API contracts between them. Every time the product team updates a spec, I'd sit there writing a Java POJO on one screen and a Python dataclass on the other — same fields, same structure, two completely different syntaxes.
One afternoon I had to update 12 model classes because an API added a shippingAddress field. By the third class, I'd already forgotten to add the @JsonProperty annotation on one field. That's when I started auto-generating both from the same JSON source.
Java POJO Generation: The Verbose Truth
Here's what a typical Java POJO looks like for a user profile endpoint:
{
"userId": "USR-001",
"email": "alex@example.com",
"isActive": true,
"roles": ["admin", "editor"],
"profile": {
"displayName": "Alex Chen",
"avatarUrl": null,
"bio": "Full-stack developer"
}
}
A well-generated Java POJO with Lombok takes this JSON and produces:
import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserProfile {
@JsonProperty("userId")
private String userId;
@JsonProperty("email")
private String email;
@JsonProperty("isActive")
private boolean isActive;
@JsonProperty("roles")
private List<String> roles;
@JsonProperty("profile")
private Profile profile;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Profile {
@JsonProperty("displayName")
private String displayName;
@JsonProperty("avatarUrl")
private String avatarUrl;
@JsonProperty("bio")
private String bio;
}
}
Notice the @JsonProperty annotations — those map the Java camelCase fields to whatever keys the JSON uses. If your API returns snake_case, the annotation takes care of it. The @Data annotation from Lombok generates getters, setters, toString, equals, and hashCode. Without Lombok, this file would be about 3x longer.
For comparison, check out our /blog/jsonformatter--json-to-go-struct-online guide to see how Go handles the same problem with struct tags.
Python Dataclass Generation: Clean and Minimal
Now let's convert the same JSON into Python dataclasses:
from dataclasses import dataclass, field
from typing import List, Optional
@dataclass
class Profile:
display_name: str
avatar_url: Optional[str] = None
bio: Optional[str] = None
@dataclass
class UserProfile:
user_id: str
email: str
is_active: bool
roles: List[str]
profile: Profile
That's it. 22 lines vs. 48 for the Java version. Python's dataclasses don't need annotations for JSON field mapping because you typically use dataclasses_json or pydantic for serialization.
With Pydantic (which I use in production), it looks even cleaner:
from pydantic import BaseModel
from typing import List, Optional
class Profile(BaseModel):
display_name: str
avatar_url: Optional[str] = None
bio: Optional[str] = None
class UserProfile(BaseModel):
user_id: str
email: str
is_active: bool
roles: List[str]
profile: Profile
Pydantic gives you validation out of the box. If the API sends is_active as a string "true" instead of a boolean, Pydantic coerces it. If a required field is missing, you get a clear validation error.
Side-by-Side: How the Same JSON Becomes Different Code
Here's what I see when I generate both from the same input using DevFormatters JSON formatter:
| Feature | Java POJO (Lombok) | Python Dataclass |
|---|---|---|
| Lines of code | ~48 for 2 classes | ~22 for 2 classes |
| Null handling | @Nullable or Optional<T> | Optional[T] |
| Field mapping | @JsonProperty | Naming convention |
| Builder pattern | @Builder | Not needed (kwargs) |
| Validation | Bean Validation annotations | Pydantic validators |
| Boilerplate | 5 annotations per class | @dataclass decorator |
The biggest gotcha I've hit is null handling. In Java, a nullable String is just a String that can be null — no compiler enforcement. In Python, you explicitly use Optional[str], which makes the nullability visible. When generating code, I always check that null fields in JSON get the right treatment in both languages.
If you're working across data formats too, our guide on /blog/jsonformatter--json-to-yaml-conversion-guide covers when and how to convert between them.
Real Project: Building a Shared Data Layer
Last quarter, I built a shared data model between a Java Spring Boot service and a Python FastAPI service. The workflow looked like this:
- Define the API contract as a sample JSON response
- Paste it into the converter
- Generate the Java POJO → drop into the Spring Boot project
- Generate the Python Pydantic model → drop into the FastAPI project
- Both sides speak the same data language
When the spec changed (and it changed 3 times), I just updated the sample JSON and regenerated both files. What used to take an hour of manual updates became a 2-minute copy-paste.
# Generated Pydantic model
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime
class OrderItem(BaseModel):
sku: str
quantity: int
unit_price: float
class Order(BaseModel):
order_id: str
items: List[OrderItem]
total: float
created_at: datetime
notes: Optional[str] = None
// Generated Java POJO
import lombok.Data;
import lombok.Builder;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.LocalDateTime;
import java.util.List;
@Data
@Builder
public class Order {
@JsonProperty("order_id")
private String orderId;
@JsonProperty("items")
private List<OrderItem> items;
@JsonProperty("total")
private double total;
@JsonProperty("created_at")
private LocalDateTime createdAt;
@JsonProperty("notes")
private String notes;
}
Common Pitfalls When Generating POJOs and Dataclasses
Date/time fields: JSON returns dates as strings. Both Java and Python generators will type them as String by default. I manually swap them to LocalDateTime (Java) or datetime (Python).
Enums: If a field only ever has 2–3 values, you might want an enum. Generators don't create enums — they'll just use String. I add enums manually after generation.
Inheritance: If you have a base class pattern (like BaseResponse<T>), generators won't know about it. You'll need to refactor the generated code to use your base types.
Generics: Java's List<User> and Python's List[User] look similar, but the converter treats them differently. Java gets List<User>, Python gets List[User] — both correct in their own language.
For flattening complex nested JSON into tabular data, see our guide on /blog/jsonformatter--convert-json-to-csv-without-code.
FAQ
Q: Can I generate Java POJOs and Python dataclasses from the same JSON?
A: Yes, tools like DevFormatters support multiple output languages from a single JSON input.
Q: Does the Java output include Lombok annotations?
A: Most converters offer Lombok support with @Data, @Builder, @NoArgsConstructor, and @AllArgsConstructor annotations.
Q: How does it handle null values in JSON?
A: Null fields become Optional[str] in Python and remain nullable reference types in Java (or Optional<T> if the converter supports it).
Q: Can I generate Pydantic models instead of plain dataclasses?
A: Many converters now offer Pydantic output, which includes validation and serialization methods.
Q: What about Jackson annotations versus Gson?
A: Most generators default to Jackson's @JsonProperty. Gson uses @SerializedName — you'd need to swap annotations manually.
Q: Does it handle nested objects in both languages?
A: Yes. Nested JSON objects become inner static classes in Java (or separate files) and nested dataclasses in Python.
Q: Can I customize the generated class names?
A: The tool uses the JSON key names to generate class names. You can rename them after generation.
Q: Is there support for Kotlin data classes?
A: Some converters support Kotlin output too, generating data class declarations with the same mapping logic.