Handling JSON Serialization and Deserialization in Java and Spring Boot
Course: Advanced Java Training
Topic: Handling JSON Serialization and Deserialization
Course Link: Advanced Java Training Course
Demo Link: Register for a Free Course Demo
Website: JustAcademy
1. Introduction to JSON Serialization and Deserialization
JSON (JavaScript Object Notation) is a lightweight, text-based data format commonly used to exchange information between web applications, REST APIs, mobile applications, and backend services. It represents data using objects, arrays, strings, numbers, Boolean values, and null.
In Java applications, data is generally represented using classes and objects. When an application needs to send Java objects to another system, it often converts those objects into JSON. When JSON data is received, the application converts it back into Java objects.
These two processes are called serialization and deserialization. In Spring Boot REST applications, Jackson is a commonly used JSON processing library, and Spring Boot automatically configures JSON support when the appropriate dependencies are available.
What Is Serialization?
Serialization is the process of converting a Java object into a format suitable for storage or transmission, such as a JSON string.
Java Object → JSON String
What Is Deserialization?
Deserialization is the reverse process. It converts JSON data into a Java object that an application can use.
JSON String → Java Object
Example
Consider a Java object representing a student:
Student student = new Student(101, "Rahul", "[email protected]");
During serialization, the object can be converted into JSON similar to the following:
{
"id": 101,
"name": "Rahul",
"email": "[email protected]"
}
When this JSON is received by another Java application, deserialization can convert it back into a Student object.
2. Learning Objectives
- Understand JSON and its role in Java applications.
- Differentiate between serialization and deserialization.
- Learn how to use the Jackson library for JSON processing.
- Convert Java objects into JSON strings and JSON strings into Java objects.
- Handle JSON request bodies and responses in Spring Boot REST APIs.
- Customize JSON output using annotations.
- Work with collections, nested objects, dates, and optional fields.
- Handle invalid JSON and data conversion errors.
- Apply security and validation best practices.
3. Understanding the JSON Data Format
JSON uses a straightforward structure that can be understood by many programming languages. It is frequently used in HTTP request bodies and API responses.
3.1 JSON Objects
A JSON object is a collection of key-value pairs enclosed in curly braces.
{
"id": 101,
"name": "Rahul",
"active": true
}
3.2 JSON Arrays
A JSON array contains an ordered collection of values enclosed in square brackets.
[
{
"id": 101,
"name": "Rahul"
},
{
"id": 102,
"name": "Priya"
}
]
3.3 Common JSON Data Types
| JSON Type |
Example |
Java Equivalent |
| String |
"Rahul" |
String |
| Number |
101 |
int, long, double, BigDecimal |
| Boolean |
true |
boolean or Boolean |
| Object |
{"city":"Mumbai"} |
Java object or Map |
| Array |
[1,2,3] |
Array, List, or another collection |
| Null |
null |
null reference |
4. What Is Jackson?
Jackson is a widely used Java library for processing JSON. It provides functionality for converting Java objects into JSON and JSON into Java objects. Spring Boot integrates JSON processing with its HTTP message conversion system.
Jackson provides classes and annotations that support object mapping, property customization, collection processing, date formatting, and custom serialization logic.
Important Jackson Components
| Component |
Purpose |
| ObjectMapper |
Common Jackson 2 API for reading and writing JSON. |
| JsonMapper |
Jackson 3 mapper API used by current Spring Boot configurations. |
| JsonNode |
Represents JSON as a tree structure. |
| @JsonProperty |
Customizes the JSON property name. |
| @JsonIgnore |
Excludes a property from normal JSON mapping. |
| @JsonFormat |
Controls formatting for supported values such as dates. |
| @JsonInclude |
Controls which properties are included in serialized JSON. |
| JsonSerializer |
Jackson 2 base class for custom serialization. |
| JsonDeserializer |
Jackson 2 base class for custom deserialization. |
Version note: The main examples in these notes use the familiar Jackson 2 API with com.fasterxml.jackson imports, commonly found in Spring Boot 3 projects. Spring Boot 4 uses Jackson 3 by default, whose core APIs use different package names and some renamed classes. Follow the API version managed by your project's Spring Boot dependencies.
5. Setting Up JSON Support in a Java Project
In a Spring Boot web application, JSON support is generally provided through the web starter and its managed dependencies.
5.1 Maven Dependency
For a typical Spring Boot REST application, add the web starter to your Maven project:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Use the Spring Boot parent or dependency management configured for your project so compatible library versions are selected automatically. Avoid adding conflicting Jackson versions without a specific reason.
5.2 Gradle Dependency
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
}
When the required JSON mapper is present, Spring Boot can normally configure it automatically. You do not need to manually create a mapper for every controller.
6. Creating a Java Model Class
A model class represents the data that an application processes. Jackson maps JSON properties to compatible Java properties or constructor parameters, depending on the class structure and configuration.
Student.java
public class Student {
private int id;
private String name;
private String email;
public Student() {
}
public Student(int id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
This class includes a no-argument constructor, fields, and getters and setters. This conventional structure works well with common Jackson 2 bean-mapping configurations.
7. Serialization Using ObjectMapper
Serialization converts a Java object into JSON. In Jackson 2, the ObjectMapper class provides methods such as writeValueAsString() and writeValue().
7.1 Converting an Object to a JSON String
import com.fasterxml.jackson.databind.ObjectMapper;
public class SerializationExample {
public static void main(String[] args) throws Exception {
Student student = new Student(
101,
"Rahul",
"[email protected]"
);
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(student);
System.out.println(json);
}
}
Expected output:
{"id":101,"name":"Rahul","email":"[email protected]"}
7.2 Pretty-Printed JSON
Pretty printing makes JSON easier to read during debugging and development.
String json = mapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(student);
System.out.println(json);
Example output:
{
"id" : 101,
"name" : "Rahul",
"email" : "[email protected]"
}
7.3 Writing JSON to a File
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
public class WriteJsonFile {
public static void main(String[] args) throws Exception {
Student student = new Student(
101,
"Rahul",
"[email protected]"
);
ObjectMapper mapper = new ObjectMapper();
mapper.writerWithDefaultPrettyPrinter()
.writeValue(new File("student.json"), student);
}
}
The writeValue() method writes JSON to a destination such as a file or output stream. Ensure that the application has the required file permissions and that sensitive information is not accidentally written to an exposed location.
8. Deserialization Using ObjectMapper
Deserialization converts JSON text into a Java object. Jackson reads the JSON properties and maps compatible values to the target Java type.
8.1 Converting JSON to a Java Object
import com.fasterxml.jackson.databind.ObjectMapper;
public class DeserializationExample {
public static void main(String[] args) throws Exception {
String json =
"{\"id\":101,\"name\":\"Rahul\","
+ "\"email\":\"[email protected]\"}";
ObjectMapper mapper = new ObjectMapper();
Student student = mapper.readValue(
json,
Student.class
);
System.out.println(student.getId());
System.out.println(student.getName());
System.out.println(student.getEmail());
}
}
Expected output:
101
Rahul
[email protected]
8.2 Reading JSON from a File
Student student = mapper.readValue(
new File("student.json"),
Student.class
);
Jackson also supports reading JSON from supported streams and other input sources. For large inputs, consider streaming or incremental parsing rather than loading an unnecessarily large JSON document into memory.
9. Serialization vs. Deserialization
| Feature |
Serialization |
Deserialization |
| Direction |
Java object to JSON |
JSON to Java object |
| Purpose |
Prepare data for transmission or storage |
Make received data available to Java code |
| Common Jackson 2 method |
writeValueAsString() |
readValue() |
| Typical REST use |
Returning an API response |
Reading an API request body |
| Potential errors |
Unsupported values or mapping configuration problems |
Malformed JSON, type mismatches, or missing required data |
10. Handling JSON Automatically in Spring Boot REST APIs
Spring MVC uses HTTP message converters to transform request and response bodies. With JSON support configured, a controller can accept a Java object using @RequestBody and return an object that Spring serializes into JSON.
10.1 Creating a REST Controller
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class StudentController {
@GetMapping("/students/sample")
public Student getStudent() {
return new Student(
101,
"Rahul",
"[email protected]"
);
}
@PostMapping("/students")
public Student createStudent(
@RequestBody Student student) {
return student;
}
}
10.2 Understanding the GET Response
When a client sends a GET request to /students/sample, Spring serializes the returned Student object into a JSON response, provided JSON is the negotiated response format.
{
"id": 101,
"name": "Rahul",
"email": "[email protected]"
}
10.3 Understanding the POST Request
A client can send a JSON request body to /students:
{
"id": 102,
"name": "Priya",
"email": "[email protected]"
}
Spring's message converter deserializes the body into a Student object before passing it to the controller method. The example returns the same object as JSON; it does not persist the student in a database.
Important: A production application should normally validate incoming data, use suitable request DTOs, call a service layer, and persist data through a repository where needed.
11. Using @RequestBody and @ResponseBody
11.1 @RequestBody
The @RequestBody annotation tells Spring MVC to read the HTTP request body and convert it into the declared Java type.
@PostMapping("/students")
public Student create(
@RequestBody Student student) {
return student;
}
11.2 @ResponseBody
The @ResponseBody annotation tells Spring MVC to write a method's return value to the HTTP response body using a suitable message converter.
@PostMapping("/students/echo")
@ResponseBody
public Student echo(@RequestBody Student student) {
return student;
}
@RestController combines @Controller and @ResponseBody, so methods in a REST controller return response bodies by default.
12. Customizing JSON Property Names
Sometimes the property names used in a Java class differ from the names expected by an API. Jackson's @JsonProperty annotation allows you to specify a JSON property name.
import com.fasterxml.jackson.annotation.JsonProperty;
public class Employee {
@JsonProperty("employee_id")
private int id;
@JsonProperty("full_name")
private String name;
public Employee() {
}
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
The JSON representation can use names such as employee_id and full_name rather than id and name.
{
"employee_id": 501,
"full_name": "Anita Sharma"
}
Use consistent naming conventions across APIs to make integration easier.
13. Excluding Fields with @JsonIgnore
The @JsonIgnore annotation excludes a property from normal Jackson mapping. This is useful when a field should not be exposed in an API representation.
import com.fasterxml.jackson.annotation.JsonIgnore;
public class User {
private String username;
@JsonIgnore
private String internalNote;
public User() {
}
public User(String username, String internalNote) {
this.username = username;
this.internalNote = internalNote;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getInternalNote() {
return internalNote;
}
public void setInternalNote(String internalNote) {
this.internalNote = internalNote;
}
}
When serialized with the default Jackson 2 configuration, the ignored property is normally omitted. Annotation behavior can be affected by configuration and access rules, so verify the actual JSON output in tests.
Security note: Never rely on serialization annotations alone as the entire security strategy for passwords, tokens, or other sensitive data. Prefer dedicated response DTOs that do not contain secrets.
14. Omitting Null Values with @JsonInclude
Sometimes APIs should omit fields whose values are null rather than returning those fields with explicit null values. The @JsonInclude annotation supports this behavior.
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Product {
private int id;
private String name;
private String description;
public Product() {
}
public Product(int id, String name, String description) {
this.id = id;
this.name = name;
this.description = description;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
If the description is null, the serialized JSON can omit that property:
{
"id": 201,
"name": "Keyboard"
}
NON_NULL excludes null-valued properties. Other inclusion options can be used when the API needs different rules for empty or default values.
15. Working with Collections and JSON Arrays
JSON arrays commonly represent lists of records. Jackson can serialize Java arrays and collections and can deserialize JSON arrays into suitable collection types.
15.1 Serializing a List
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.List;
List<Student> students = Arrays.asList(
new Student(101, "Rahul", "[email protected]"),
new Student(102, "Priya", "[email protected]")
);
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(students);
System.out.println(json);
15.2 Deserializing a JSON Array
When reading a JSON array into a generic collection, use a type reference so Jackson knows the element type.
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
String json =
"[{\"id\":101,\"name\":\"Rahul\","
+ "\"email\":\"[email protected]\"},"
+ "{\"id\":102,\"name\":\"Priya\","
+ "\"email\":\"[email protected]\"}]";
ObjectMapper mapper = new ObjectMapper();
List<Student> students = mapper.readValue(
json,
new TypeReference<List<Student>>() {}
);
for (Student student : students) {
System.out.println(student.getName());
}
Using TypeReference preserves the generic collection element type that would otherwise be lost through Java type erasure.
16. Handling Nested JSON Objects
Real-world JSON often contains nested objects. Jackson can map nested JSON structures to Java classes when the model structure and property names are compatible.
16.1 Address Class
public class Address {
private String city;
private String country;
public Address() {
}
public Address(String city, String country) {
this.city = city;
this.country = country;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}
16.2 Student Class with Address
public class Student {
private int id;
private String name;
private Address address;
public Student() {
}
public Student(int id, String name, Address address) {
this.id = id;
this.name = name;
this.address = address;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
16.3 Example Nested JSON
{
"id": 101,
"name": "Rahul",
"address": {
"city": "Mumbai",
"country": "India"
}
}
The address property maps to an Address object. This approach helps represent structured data without flattening every property into a single class.
17. Handling Dates and Time Values
Date and time serialization requires attention because Java date-time types can have different meanings. A date such as a birthday differs from a timestamp that identifies a specific instant.
For modern Java applications, types from java.time, such as LocalDate, LocalDateTime, and Instant, are generally preferable to legacy date types.
17.1 Example with LocalDate
import java.time.LocalDate;
public class Employee {
private int id;
private String name;
private LocalDate joiningDate;
public Employee() {
}
public Employee(int id, String name, LocalDate joiningDate) {
this.id = id;
this.name = name;
this.joiningDate = joiningDate;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDate getJoiningDate() {
return joiningDate;
}
public void setJoiningDate(LocalDate joiningDate) {
this.joiningDate = joiningDate;
}
}
For Jackson 2, Java time support is commonly provided by the jackson-datatype-jsr310 module. Spring Boot's managed dependencies and auto-configuration normally handle the required setup for typical web applications.
An ISO-style date may be represented as:
{
"id": 501,
"name": "Anita",
"joiningDate": "2026-10-24"
}
The exact representation depends on the mapper configuration. Define and document the expected format for API contracts, especially when dealing with time zones or timestamps.
18. Working with Enums in JSON
Java enums represent a fixed set of values, such as order statuses or user roles. Jackson commonly serializes enum values as strings based on their names.
public enum OrderStatus {
PENDING,
PROCESSING,
COMPLETED,
CANCELLED
}
Example JSON:
{
"orderId": 1001,
"status": "PROCESSING"
}
Ensure that incoming enum values match the expected values or are handled through an intentional mapping strategy. Unknown or misspelled values can cause deserialization errors depending on the mapper configuration.
19. Custom Serialization
Custom serialization is useful when the default object-to-JSON mapping does not meet an application's requirements. Examples include masking selected data, changing a complex value into a simpler representation, or applying a specific output format.
For Jackson 2, a custom serializer extends JsonSerializer and implements its serialization method.
19.1 Example Custom Serializer
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class MoneySerializer extends JsonSerializer<Double> {
@Override
public void serialize(
Double value,
JsonGenerator generator,
SerializerProvider serializers) throws IOException {
generator.writeString(
String.format(java.util.Locale.ROOT, "%.2f", value)
);
}
}
19.2 Applying the Custom Serializer
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
public class Invoice {
private int id;
@JsonSerialize(using = MoneySerializer.class)
private Double amount;
public Invoice() {
}
public Invoice(int id, Double amount) {
this.id = id;
this.amount = amount;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Double getAmount() {
return amount;
}
public void setAmount(Double amount) {
this.amount = amount;
}
}
The amount can be serialized as a string with two decimal places, for example "1250.50". Be aware that representing numbers as strings changes the JSON data type, so follow the API contract and use a decimal type such as BigDecimal for financial calculations where appropriate.
20. Custom Deserialization
Custom deserialization allows an application to transform incoming JSON values into Java values using custom rules. It is useful when the external data format differs from the internal model.
20.1 Example Custom Deserializer
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
public class TrimmedStringDeserializer
extends JsonDeserializer<String> {
@Override
public String deserialize(
JsonParser parser,
DeserializationContext context) throws IOException {
String value = parser.getValueAsString();
return value == null ? null : value.trim();
}
}
20.2 Applying the Deserializer
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
public class Customer {
private int id;
@JsonDeserialize(using = TrimmedStringDeserializer.class)
private String name;
public Customer() {
}
public Customer(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
If the incoming JSON contains "name": " Priya ", the custom deserializer trims the surrounding whitespace before assigning the value. In a production project, also decide how to handle blank strings, missing fields, and explicit null values.
21. Working with JsonNode for Dynamic JSON
Sometimes the structure of incoming JSON is not known in advance. Jackson's JsonNode tree model allows code to inspect JSON properties without first defining a complete Java class.
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonTreeExample {
public static void main(String[] args) throws Exception {
String json =
"{\"id\":101,\"name\":\"Rahul\","
+ "\"address\":{\"city\":\"Mumbai\"}}";
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);
int id = root.path("id").asInt();
String name = root.path("name").asText();
String city = root.path("address")
.path("city")
.asText();
System.out.println(id);
System.out.println(name);
System.out.println(city);
}
}
Using path() avoids a null result when a property is missing, returning a missing-node representation instead. However, applications should still validate whether required fields exist and whether their types are correct.
22. Handling JSON Errors and Exceptions
JSON processing can fail when input is malformed, a value has the wrong type, a required constructor property is missing, or the target Java class cannot be created. A REST API should handle these failures consistently.
22.1 Common Problems
| Problem |
Example |
Recommended Action |
| Malformed JSON |
Missing quotation marks or braces |
Return a clear client error without exposing internal details. |
| Type mismatch |
A string is supplied for a numeric property |
Validate input and define consistent conversion rules. |
| Unknown property |
JSON contains an unexpected field |
Choose an intentional policy for strict or tolerant input. |
| Missing required value |
Required name is absent |
Use validation and suitable DTO constraints. |
| Unsupported Java type |
Mapper lacks the required module |
Register the correct module or use a supported type. |
| Unexpected date format |
Input date differs from the API contract |
Document and validate the expected format. |
22.2 Handling Invalid Request Bodies in Spring Boot
Spring MVC commonly raises an HttpMessageNotReadableException when it cannot read or convert a request body. A global exception handler can return a controlled error response.
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<String> handleInvalidJson(
HttpMessageNotReadableException exception) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body("Invalid or unreadable JSON request body.");
}
}
This example returns a simple text error response. A production API may instead return a structured error DTO containing an error code, a safe message, and a request identifier. Avoid returning raw exception messages, stack traces, or internal implementation details to clients.
23. Validating Incoming JSON
Successful deserialization does not mean that incoming data is valid. For example, a name can be deserialized correctly while still being blank. Bean Validation can enforce rules after JSON has been converted into a Java object.
23.1 Creating a Validated Request DTO
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Positive;
public class StudentRequest {
@Positive
private int id;
@NotBlank
private String name;
@NotBlank
@Email
private String email;
public StudentRequest() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
23.2 Using @Valid in a Controller
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class StudentApiController {
@PostMapping("/api/students")
public ResponseEntity<StudentRequest> createStudent(
@Valid @RequestBody StudentRequest request) {
return ResponseEntity.ok(request);
}
}
Add the validation starter to the project when required. For example, Spring Boot applications commonly use spring-boot-starter-validation. Validation errors should be handled with a consistent response structure.
24. Security Best Practices for JSON Processing
- Validate incoming data: Check required fields, lengths, ranges, formats, and business rules.
- Use DTOs: Avoid binding external JSON directly to sensitive persistence entities.
- Protect confidential values: Do not return passwords, authentication tokens, private keys, or internal secrets in JSON responses.
- Set request size limits: Prevent oversized request bodies from consuming excessive memory or processing time.
- Handle parser errors safely: Return controlled client-facing errors and keep diagnostic details in protected logs.
- Review polymorphic deserialization: Avoid unsafe type-handling configurations for untrusted input.
- Control unknown properties intentionally: Decide whether unexpected fields should be rejected or ignored according to the API contract.
- Use HTTPS: Protect JSON data while it travels between clients and servers.
- Apply authorization: Valid JSON does not prove that a client is allowed to access or modify a resource.
- Test edge cases: Include nulls, missing fields, incorrect types, very long strings, and unexpected nested structures.
25. Testing JSON Serialization and Deserialization
Testing helps confirm that Java objects produce the expected JSON and that incoming JSON maps correctly to Java objects.
25.1 Unit Test for Serialization
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
class StudentJsonTest {
@Test
void shouldSerializeStudent() throws Exception {
ObjectMapper mapper = new ObjectMapper();
Student student = new Student(
101,
"Rahul",
"[email protected]"
);
String json = mapper.writeValueAsString(student);
assertTrue(json.contains("\"id\":101"));
assertTrue(json.contains("\"name\":\"Rahul\""));
}
}
25.2 Unit Test for Deserialization
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class StudentDeserializationTest {
@Test
void shouldDeserializeStudent() throws Exception {
ObjectMapper mapper = new ObjectMapper();
String json =
"{\"id\":101,\"name\":\"Rahul\","
+ "\"email\":\"[email protected]\"}";
Student student = mapper.readValue(
json,
Student.class
);
assertEquals(101, student.getId());
assertEquals("Rahul", student.getName());
assertEquals("[email protected]", student.getEmail());
}
}
For more robust tests, compare parsed JSON structures rather than relying on property ordering or exact whitespace. Also test malformed JSON, null handling, unknown fields, date formats, and custom serializers.
26. Testing REST APIs with Postman or curl
26.1 Test a GET Endpoint
Send a GET request to:
http://localhost:8080/students/sample
Confirm that the response body contains the expected JSON and that the HTTP status code is appropriate.
26.2 Test a POST Endpoint
Send a POST request to:
http://localhost:8080/students
Set the request header to Content-Type: application/json and provide the following body:
{
"id": 103,
"name": "Amit",
"email": "[email protected]"
}
Example using curl:
curl -X POST "http://localhost:8080/students" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"id":103,"name":"Amit","email":"[email protected]"}'
Check the status code, response body, and error behavior for invalid inputs. If the endpoint returns the submitted object, remember that the demonstration controller is only echoing data rather than saving it to a database.
27. Common Mistakes and Troubleshooting
| Mistake |
Why It Happens |
Solution |
| Invalid JSON syntax |
Missing commas, quotes, or braces |
Validate the JSON body before sending it. |
| Wrong Content-Type |
The request does not identify its body as JSON |
Use Content-Type: application/json. |
| Missing setters or compatible constructors |
The target class is not configured for the selected mapping approach |
Review constructors, getters, setters, annotations, and mapper configuration. |
| Unexpected property names |
JSON keys differ from Java property names |
Use consistent names or @JsonProperty. |
| Infinite recursion |
Bidirectional object relationships refer back to each other |
Use DTOs or configure relationship serialization carefully. |
| Sensitive data leakage |
Internal entity fields are serialized unintentionally |
Return dedicated response DTOs and review output fields. |
| Date parsing errors |
The input format or Java time module is incorrect |
Configure date-time support and document the expected format. |
| Unexpected null fields |
Input omitted fields or supplied explicit null values |
Validate required data and define null-handling rules. |
| Large JSON payloads |
Requests exceed practical resource limits |
Apply size limits and use streaming where appropriate. |
28. Best Practices for JSON Serialization and Deserialization
- Use the JSON mapper and dependency versions managed by the project's framework.
- Prefer automatic Spring Boot JSON conversion for normal REST controller requests and responses.
- Use explicit DTOs to define the public API contract.
- Keep serialization rules predictable and documented.
- Use
BigDecimal for precise decimal arithmetic where appropriate, particularly for monetary values.
- Use suitable Java time types and consistent date-time formats.
- Use custom serializers and deserializers only when default mapping is insufficient.
- Configure unknown-property handling based on compatibility and validation requirements.
- Do not create a new mapper for every request; use Spring's configured mapper or a suitably managed mapper instance.
- Write unit and integration tests for successful and unsuccessful cases.
- Do not expose stack traces or sensitive internal values in API errors.
- Keep API responses stable when changing Java model classes.
29. Practical Exercise
Exercise: Build a Student Management REST API that accepts and returns JSON data.
Requirements
- Create a Student model or request DTO with ID, name, email, and joining date.
- Implement a GET endpoint that returns a student as JSON.
- Implement a POST endpoint that accepts a JSON student object.
- Serialize a list of students into a JSON array.
- Deserialize a JSON array into a Java collection using
TypeReference.
- Use
@JsonProperty to customize at least one JSON field name.
- Use validation annotations to reject invalid student data.
- Return a controlled error response when the JSON request body is malformed.
- Write unit tests for serialization and deserialization.
- Test both valid and invalid requests with Postman or curl.
Expected Result
The application should convert Java objects to JSON responses, read JSON request bodies into Java objects, validate incoming data, and handle common conversion errors without exposing internal details.
30. Interview Questions and Answers
Q1. What is JSON serialization?
Answer: Serialization is the process of converting a Java object into JSON or another representation suitable for transmission or storage.
Q2. What is JSON deserialization?
Answer: Deserialization converts JSON data into a Java object that the application can process.
Q3. What is Jackson?
Answer: Jackson is a Java data-processing library that supports JSON parsing, generation, and object mapping. Spring Boot integrates Jackson when the appropriate dependencies and configuration are present.
Q4. What is ObjectMapper?
Answer: In Jackson 2, ObjectMapper is a central API for converting Java objects to JSON and JSON into Java objects.
Q5. What does writeValueAsString() do?
Answer: It serializes a Java value into a JSON string.
Q6. What does readValue() do?
Answer: It reads JSON input and converts it into a specified Java type.
Q7. How does Spring Boot automatically handle JSON?
Answer: Spring MVC uses configured HTTP message converters to deserialize request bodies and serialize response bodies when the appropriate JSON mapper is available.
Q8. What is the purpose of @RequestBody?
Answer: It instructs Spring MVC to read the HTTP request body and convert it into the declared Java type.
Q9. What is the purpose of @JsonProperty?
Answer: It customizes the JSON property name used for a Java property or constructor parameter.
Q10. What does @JsonIgnore do?
Answer: It excludes a property from normal Jackson serialization and deserialization behavior, subject to mapper configuration and access rules.
Q11. How do you serialize a list of Java objects?
Answer: Pass the list to the mapper's serialization method. Jackson serializes the collection as a JSON array.
Q12. How do you deserialize a JSON array into a List of objects?
Answer: Use readValue() with a TypeReference<List<Student>> so Jackson knows the element type.
Q13. Why use a custom serializer?
Answer: A custom serializer is useful when a value needs a specific output representation that default mapping cannot provide.
Q14. How should malformed JSON be handled in Spring Boot?
Answer: Handle request-body conversion exceptions, return a controlled client error, and avoid exposing stack traces or sensitive internal details.
Q15. Why should request DTOs be used?
Answer: DTOs define the accepted input and returned output, reduce accidental exposure of internal fields, and help separate API contracts from persistence models.
Q16. Does successful deserialization guarantee valid input?
Answer: No. Data may be syntactically valid but still violate required-field, format, range, authorization, or business rules. Validation is still necessary.
Q17. What is JsonNode?
Answer: JsonNode represents JSON as a tree, allowing an application to inspect dynamic JSON structures without first mapping them to a complete Java class.
Q18. How can null properties be omitted from JSON?
Answer: Use @JsonInclude(JsonInclude.Include.NON_NULL) or an appropriate mapper-level inclusion setting.
31. Quick Revision Checklist
- Serialization converts Java objects to JSON.
- Deserialization converts JSON to Java objects.
- Jackson is a commonly used JSON mapping library in Spring Boot applications.
- Jackson 2 commonly uses
ObjectMapper; current Jackson 3 configurations use the JsonMapper API.
writeValueAsString() creates a JSON string.
readValue() converts JSON input into a target type.
@RequestBody supports request-body conversion.
@RestController supports response-body serialization.
@JsonProperty customizes property names.
@JsonIgnore excludes properties from normal mapping.
@JsonInclude controls property inclusion.
TypeReference helps deserialize generic collections.
- Custom serializers and deserializers support specialized mapping requirements.
- Validation and exception handling are essential for reliable APIs.
- DTOs, secure error responses, and automated tests improve application quality.
32. Conclusion
JSON serialization and deserialization are essential skills for building Java applications that communicate through REST APIs. Serialization transforms Java objects into JSON responses, while deserialization converts incoming JSON into objects that application code can validate and process.
With Jackson and Spring Boot, much of this conversion can happen automatically through HTTP message converters. Understanding mapper APIs, annotations, nested objects, collections, date-time formats, custom mapping, validation, and exception handling makes it easier to build reliable and maintainable APIs.
To learn more about Java, Spring, and backend development, explore the Advanced Java Training Course, visit the JustAcademy website, or register for a course demo.