📌 Introduction
JSON serialization is a fundamental part of modern Java applications — converting Java objects into JSON and back. Jackson is the most widely used library for this purpose in the Java ecosystem, offering powerful, flexible, and performant serialization mechanisms.
In this article, we'll explore:
- Basic Jackson setup
- Custom serialization & deserialization
- A real-world one-to-many example:
CarDTO→CarAttributeDTO
📚 References:
⚙️ 1. Maven Dependency
Add Jackson to your pom.xml:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
🏗️ 2. Define the DTOs
CarAttributeDTO — The "Many" Side
public class CarAttributeDTO {
private String attributeName; // e.g. "color", "engine", "transmission"
private String attributeValue; // e.g. "red", "V8", "automatic"
// Constructors
public CarAttributeDTO() {}
public CarAttributeDTO(String attributeName, String attributeValue) {
this.attributeName = attributeName;
this.attributeValue = attributeValue;
}
// Getters & Setters
public String getAttributeName() { return attributeName; }
public void setAttributeName(String attributeName) {
this.attributeName = attributeName;
}
public String getAttributeValue() { return attributeValue; }
public void setAttributeValue(String attributeValue) {
this.attributeValue = attributeValue;
}
}
CarDTO — The "One" Side (with One-to-Many relationship)
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.List;
@JsonInclude(JsonInclude.Include.NON_NULL) // Excludes null fields from JSON
public class CarDTO {
@JsonProperty("car_id")
private Long id;
@JsonProperty("brand")
private String brand; // e.g. "BMW"
@JsonProperty("model")
private String model; // e.g. "M3"
@JsonProperty("year")
private int year; // e.g. 2024
@JsonProperty("attributes")
private List<CarAttributeDTO> attributes; // One-to-Many
// Constructors
public CarDTO() {}
public CarDTO(Long id, String brand, String model,
int year, List<CarAttributeDTO> attributes) {
this.id = id;
this.brand = brand;
this.model = model;
this.year = year;
this.attributes = attributes;
}
// Getters & Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getBrand() { return brand; }
public void setBrand(String brand) { this.brand = brand; }
public String getModel() { return model; }
public void setModel(String model) { this.model = model; }
public int getYear() { return year; }
public void setYear(int year) { this.year = year; }
public List<CarAttributeDTO> getAttributes() { return attributes; }
public void setAttributes(List<CarAttributeDTO> attributes) {
this.attributes = attributes;
}
}
🔄 3. Basic Serialization (Object → JSON)
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.util.List;
public class JacksonSerializationExample {
public static void main(String[] args) throws Exception {
// Build the CarDTO with attributes (One-to-Many)
List<CarAttributeDTO> attributes = List.of(
new CarAttributeDTO("color", "Frozen Black"),
new CarAttributeDTO("engine", "3.0L TwinPower Turbo"),
new CarAttributeDTO("transmission", "8-Speed Automatic"),
new CarAttributeDTO("horsepower", "503 HP")
);
CarDTO car = new CarDTO(1L, "BMW", "M3 Competition", 2024, attributes);
// Configure ObjectMapper
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT); // Pretty print
// Serialize to JSON String
String json = mapper.writeValueAsString(car);
System.out.println(json);
}
}
📤 Output JSON:
{
"car_id": 1,
"brand": "BMW",
"model": "M3 Competition",
"year": 2024,
"attributes": [
{
"attributeName": "color",
"attributeValue": "Frozen Black"
},
{
"attributeName": "engine",
"attributeValue": "3.0L TwinPower Turbo"
},
{
"attributeName": "transmission",
"attributeValue": "8-Speed Automatic"
},
{
"attributeName": "horsepower",
"attributeValue": "503 HP"
}
]
}
🔁 4. Deserialization (JSON → Object)
String jsonInput = """
{
"car_id": 2,
"brand": "BMW",
"model": "X5",
"year": 2023,
"attributes": [
{ "attributeName": "color", "attributeValue": "Alpine White" },
{ "attributeName": "engine", "attributeValue": "4.4L V8" }
]
}
""";
ObjectMapper mapper = new ObjectMapper();
CarDTO car = mapper.readValue(jsonInput, CarDTO.class);
System.out.println("Brand : " + car.getBrand());
System.out.println("Model : " + car.getModel());
System.out.println("Attrs : " + car.getAttributes().size());
🎨 5. Custom Serializer
Sometimes you need full control over the JSON output. Use a custom serializer to reshape the attributes list into a flat key-value map:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
public class CarDTOSerializer extends StdSerializer<CarDTO> {
public CarDTOSerializer() {
super(CarDTO.class);
}
@Override
public void serialize(CarDTO car,
JsonGenerator gen,
SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeNumberField("car_id", car.getId());
gen.writeStringField("brand", car.getBrand());
gen.writeStringField("model", car.getModel());
gen.writeNumberField("year", car.getYear());
// 🔑 Flatten attributes into a key-value object
gen.writeObjectFieldStart("attributes");
if (car.getAttributes() != null) {
for (CarAttributeDTO attr : car.getAttributes()) {
gen.writeStringField(
attr.getAttributeName(),
attr.getAttributeValue()
);
}
}
gen.writeEndObject();
gen.writeEndObject();
}
}
Register the Custom Serializer:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
SimpleModule module = new SimpleModule();
module.addSerializer(CarDTO.class, new CarDTOSerializer());
mapper.registerModule(module);
String json = mapper.writeValueAsString(car);
System.out.println(json);
📤 Output with Custom Serializer (flattened attributes):
{
"car_id": 1,
"brand": "BMW",
"model": "M3 Competition",
"year": 2024,
"attributes": {
"color": "Frozen Black",
"engine": "3.0L TwinPower Turbo",
"transmission": "8-Speed Automatic",
"horsepower": "503 HP"
}
}
🛡️ 6. Useful Jackson Annotations Summary
| Annotation | Purpose |
|---|---|
@JsonProperty("name") | Rename a field in JSON |
@JsonInclude(NON_NULL) | Exclude null fields from output |
@JsonIgnore | Completely exclude a field from serialization |
@JsonAlias | Accept multiple names during deserialization |
@JsonSerialize | Attach a custom serializer to a field/class |
@JsonDeserialize | Attach a custom deserializer to a field/class |
@JsonFormat | Format dates, numbers, etc. |
✅ 7. Best Practices
🔹 Reuse
ObjectMapper— it is thread-safe and expensive to create
🔹 Use@JsonInclude(NON_NULL)to keep JSON payloads clean
🔹 Prefer DTOs over Entities — never serialize JPA entities directly
🔹 Use custom serializers when you need full control over output format
🔹 Validate deserialized objects using Bean Validation (@Valid)
🎯 Conclusion
Jackson provides a rich, flexible API to handle JSON serialization in Java. By combining annotations, custom serializers, and the ObjectMapper, you can cleanly model complex relationships like Car → Attributes (one-to-many) and produce well-structured, efficient JSON payloads.
📚 Further Reading: