Java JSON Serialization Guide

Java ~4 min read
JavaJSONJacksonGsonMoshiSerialization

Comprehensive comparison of Jackson, Gson, and Fastjson libraries, covering annotation configuration, generic handling, polymorphic deserialization, and performance optimization.

Library Comparison Overview

The Java ecosystem offers three major JSON libraries, each with distinct strengths:

FeatureJacksonGsonMoshi
PerformanceExcellentGoodVery Good
Streaming APIYesNoYes (Okio)
Annotation SupportExtensiveModerateModerate
Kotlin SupportGoodBasicExcellent
CommunityLargestLargeGrowing

Jackson: The Industry Standard

Jackson is the most widely used JSON library in Java, offering both tree model and data binding approaches.

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);

// Serialize
User user = new User("Alice", 30);
String json = mapper.writeValueAsString(user);
// {"name":"Alice","age":30}

// Deserialize
User parsed = mapper.readValue(json, User.class);

Key Annotations

public class User {
    @JsonProperty("user_name")
    private String name;

    @JsonIgnore
    private String password;

    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate birthDate;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String optionalField;
}

Gson: Google’s Lightweight Option

Gson offers a simpler API with less configuration overhead.

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

Gson gson = new GsonBuilder()
    .setPrettyPrinting()
    .setDateFormat("yyyy-MM-dd")
    .create();

// Serialize
String json = gson.toJson(user);

// Deserialize
User parsed = gson.fromJson(json, User.class);

Custom Type Adapters

public class LocalDateAdapter implements JsonSerializer<LocalDate>, JsonDeserializer<LocalDate> {
    @Override
    public JsonElement serialize(LocalDate date, Type type, JsonSerializationContext context) {
        return new JsonPrimitive(date.toString());
    }

    @Override
    public LocalDate deserialize(JsonElement element, Type type, JsonDeserializationContext context) {
        return LocalDate.parse(element.getAsString());
    }
}

Moshi: Modern and Kotlin-Friendly

Moshi is designed with Kotlin in mind, offering null-safety and code generation. While it works in pure Java, it is most commonly used in Kotlin projects — for Java-only codebases, Jackson or Gson are typically preferred. The examples below show Kotlin syntax (Moshi’s primary use case), but a Java equivalent follows each example.

Kotlin (primary use case):

@JsonClass(generateAdapter = true)
data class User(
    val name: String,
    val age: Int,
    val email: String? = null
)

val moshi = Moshi.Builder().build()
val adapter = moshi.adapter(User::class.java)

val json = adapter.toJson(user)
val parsed = adapter.fromJson(json)

Java equivalent:

import com.squareup.moshi.Moshi;
import com.squareup.moshi.JsonAdapter;

Moshi moshi = new Moshi.Builder().build();
JsonAdapter<User> adapter = moshi.adapter(User.class);

String json = adapter.toJson(user);
User parsed = adapter.fromJson(json);

Spring Boot Integration

Spring Boot uses Jackson by default. Configure it in application.yml:

spring:
  jackson:
    serialization:
      indent-output: true
      write-dates-as-timestamps: false
    deserialization:
      fail-on-unknown-properties: false
    default-property-inclusion: non_null

Custom ObjectMapper bean:

@Bean
public ObjectMapper objectMapper() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JavaTimeModule());
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    return mapper;
}

Polymorphic Deserialization

Handling inheritance hierarchies during deserialization requires special configuration.

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
    @JsonSubTypes.Type(value = Dog.class, name = "dog"),
    @JsonSubTypes.Type(value = Cat.class, name = "cat")
})
public abstract class Animal { }

public class Dog extends Animal {
    public String breed;
}

public class Cat extends Animal {
    public boolean indoor;
}

Performance Tips

  1. Reuse ObjectMapper — Creating a new instance is expensive
  2. Use @JsonCreator for immutable objects instead of setters
  3. Stream large arrays with JsonParser instead of loading into memory
  4. Consider Blackbird module (modern replacement for Afterburner) for bytecode-optimized serialization
  5. Use JsonFactory reuse across multiple ObjectMapper instances
// Stream processing for large JSON arrays
try (JsonParser parser = mapper.getFactory().createParser(inputStream)) {
    parser.nextToken(); // START_ARRAY
    while (parser.nextToken() == JsonToken.START_OBJECT) {
        Item item = mapper.readValue(parser, Item.class);
        processItem(item);
    }
}

JSON-B (Jakarta EE)

JSON-B is the standard JSON binding API for Jakarta EE:

import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;

// Basic usage
Jsonb jsonb = JsonbBuilder.create();
String json = jsonb.toJson(user);

// Deserialize
User parsed = jsonb.fromJson(json, User.class);

Custom configuration:

import jakarta.json.bind.JsonbConfig;

JsonbConfig config = new JsonbConfig()
    .withFormatting(true)
    .withNullValues(true)
    .withDateFormat("yyyy-MM-dd", Locale.US);

Jsonb customJsonb = JsonbBuilder.create(config);
String json = customJsonb.toJson(user);

Streaming Large Files

Process gigabyte-scale JSON without loading into memory:

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;

try (JsonParser parser = new JsonFactory().createParser(new File("large.json"))) {
    if (parser.nextToken() != JsonToken.START_ARRAY) {
        throw new IllegalStateException("Expected array");
    }

    while (parser.nextToken() == JsonToken.START_OBJECT) {
        Item item = mapper.readValue(parser, Item.class);
        processItem(item); // Process one at a time
    }
}

JSON Schema Validation

Using everit-org/json-schema:

import org.everit.json.schema.Schema;
import org.everit.json.schema.loader.SchemaLoader;
import org.json.JSONObject;
import org.json.JSONArray;

JSONObject schemaJson = new JSONObject(new JSONTokener(
    getClass().getResourceAsStream("/schema.json")));
Schema schema = SchemaLoader.load(schemaJson);

JSONObject data = new JSONObject("{\"name\": \"Alice\", \"age\": 30}");
schema.validate(data); // Throws ValidationException if invalid

Error Handling

JSON parsing can fail for many reasons — malformed input, type mismatches, or unexpected fields. Always wrap deserialization in a try-catch block to handle these gracefully.

import com.fasterxml.jackson.core.JsonProcessingException;

try {
    User user = mapper.readValue(json, User.class);
} catch (JsonProcessingException e) {
    System.err.println("JSON error at line " + e.getLocation().getLineNr());
    System.err.println("Message: " + e.getOriginalMessage());
}

Jackson’s JsonProcessingException provides the exact line and column where the error occurred, making it straightforward to locate the problem in large payloads. Gson throws JsonSyntaxException and Moshi throws JsonDataException — both carry similar diagnostic information.

Maven/Gradle Dependencies

To get started with any of these libraries, add the appropriate dependency to your project:

LibraryMaven Artifact
Jacksoncom.fasterxml.jackson.core:jackson-databind
Gsoncom.google.code.gson:gson
Moshicom.squareup.moshi:moshi

Maven example (Jackson):

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.2</version>
</dependency>

Gradle example (Jackson):

implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.2'

JSON API Comparison Across Languages

Language Library Serialize Deserialize Pretty Print Config
Python json(内置) json.dumps(obj) json.loads(str) json.dumps(obj, indent=2) ensure_ascii, default, cls
JavaScript JSON(内置) JSON.stringify(obj) JSON.parse(str) JSON.stringify(obj, null, 2) replacer, space
Java Jackson / Gson mapper.writeValueAsString(obj) mapper.readValue(str, Class.class) mapper.writerWithDefaultPrettyPrinter() 注解、Module、Feature
Go encoding/json(内置) json.Marshal(obj) json.Unmarshal(data, &obj) json.MarshalIndent(obj, "", " ") struct tag
C# System.Text.Json(内置) JsonSerializer.Serialize(obj) JsonSerializer.Deserialize<T>(str) WriteIndented = true JsonSerializerOptions
TypeScript JSON(内置) JSON.stringify(obj) JSON.parse(str) JSON.stringify(obj, null, 2) replacer, space
Rust serde_json serde_json::to_string(&obj) serde_json::from_str::<T>(str) serde_json::to_string_pretty(&obj) serde attributes, custom (de)serializers

Frequently Asked Questions (FAQ)

How to handle datetime types in JSON serialization?

Most language JSON libraries do not support datetime types by default. The common approach is to serialize as ISO 8601 format strings (e.g. "2026-07-15T10:30:00Z") or Unix timestamps, then convert back to datetime objects during deserialization.

How to ignore null values or empty fields in JSON serialization?

Different languages have different implementations: Python can filter None values in custom encoders; JavaScript can use replacer functions; Java Jackson uses @JsonInclude annotations; Go uses omitempty struct tags; C# sets DefaultIgnoreCondition.

How to handle circular references in JSON serialization?

Circular references are a common pitfall. Standard JSON libraries usually throw exceptions or cause stack overflow. Solutions include using @JsonIdentityInfo (Java Jackson), implementing custom serializers, or converting to DTOs before serialization.

Why are my private fields not being serialized?

JSON serialization libraries typically only serialize public fields or properties with getters. This is a security consideration. You can use annotations like @JsonProperty (Java Jackson) or [JsonInclude] (C#) to include non-public members.

What are the performance optimization tips for JSON serialization?

1) Reuse serializer instances; 2) Use streaming APIs for large data; 3) Use compile-time generation (C# Source Generator, Go easyjson); 4) Avoid excessive nesting; 5) Use database pagination for large JSON.

Recommended Tools

These tools can help you handle JSON data more efficiently in Java development:

Related Articles