Java JSON with Jackson: ObjectMapper, @JsonProperty & Spring Boot
Last updated:
Java JSON serialization is dominated by Jackson — objectMapper.readValue(jsonStr, MyClass.class) deserializes JSON to a typed POJO, and objectMapper.writeValueAsString(obj) serializes it back, with zero-configuration for classes following the JavaBean convention (public getters/setters). Jackson ObjectMapper is thread-safe and expensive to create — instantiate once as a singleton (Spring Boot auto-configures it as a bean); a shared ObjectMapper processes a 10 KB JSON object in ~0.1 ms, while creating a new ObjectMapper per request adds ~5 ms overhead. This guide covers Jackson ObjectMapper configuration, @JsonProperty and @JsonIgnore annotations, custom serializers and deserializers, JsonNode for dynamic JSON, Java records with Jackson, Spring Boot JSON auto-configuration, and comparing Jackson with Gson and JSON-B.
ObjectMapper Basics: readValue and writeValueAsString
ObjectMapper is the entry point for all Jackson serialization and deserialization. Create it once and reuse it — the instance is thread-safe, and its internal caches (type introspection, deserializer lookups) are only built once. readValue(String, Class) deserializes a JSON string to a POJO; readValue(InputStream, TypeReference) handles generic types; writeValueAsString(obj) serializes to a string; writeValue(File, obj) writes directly to a file without buffering the full string in memory.
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.List;
import java.io.File;
import java.io.InputStream;
// ── Singleton pattern ─────────────────────────────────────────
// Create once, reuse everywhere — ObjectMapper is thread-safe
public class JacksonConfig {
private static final ObjectMapper MAPPER = new ObjectMapper();
public static ObjectMapper get() { return MAPPER; }
}
// ── Basic serialization ───────────────────────────────────────
public class User {
private int id;
private String name;
private String email;
// public getters/setters (JavaBean convention)
public int getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
public void setId(int id) { this.id = id; }
public void setName(String name) { this.name = name; }
public void setEmail(String email) { this.email = email; }
}
ObjectMapper mapper = JacksonConfig.get();
// Serialize object to JSON string
User user = new User();
user.setId(1);
user.setName("Alice");
user.setEmail("alice@example.com");
String json = mapper.writeValueAsString(user);
// {"id":1,"name":"Alice","email":"alice@example.com"}
// Pretty-printed output
String pretty = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);
// Write to file (no intermediate string allocation — more memory-efficient)
mapper.writeValue(new File("user.json"), user);
// ── Basic deserialization ─────────────────────────────────────
String jsonStr = "{\"id\":1,\"name\":\"Alice\",\"email\":\"alice@example.com\"}";
User parsed = mapper.readValue(jsonStr, User.class);
// ── Generic types: List<User> ─────────────────────────────────
// Cannot use List.class — type erasure loses the <User> parameter
// WRONG: mapper.readValue(json, List.class) → List<LinkedHashMap>
// RIGHT: use TypeReference to preserve the generic type
String jsonArray = "[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"}]";
List<User> users = mapper.readValue(jsonArray, new TypeReference<List<User>>() {});
// ── InputStream deserialization (HTTP response body) ──────────
// Use readValue(InputStream) to avoid loading the full response into a string
try (InputStream is = httpResponse.getBody()) {
User fromStream = mapper.readValue(is, User.class);
}
// ── Map deserialization ───────────────────────────────────────
String mapJson = "{\"key1\":\"value1\",\"key2\":\"value2\"}";
java.util.Map<String, String> map =
mapper.readValue(mapJson, new TypeReference<java.util.Map<String, String>>() {});Jackson maps JSON keys to Java fields by matching key names to getter/setter method names after removing the get/set prefix and lowercasing the first letter — getName() maps to JSON key name. Fields without public getters are invisible to Jackson by default. To serialize fields directly (without getters), configure the mapper: mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY). For collections, always use TypeReference — passing List.class returns a List<LinkedHashMap> due to type erasure, not a List<User>.
Jackson Annotations: @JsonProperty, @JsonIgnore, @JsonInclude
Jackson annotations are the primary mechanism for controlling JSON mapping without modifying the ObjectMapper globally. @JsonProperty renames a field in the JSON output, @JsonIgnore excludes a field entirely, and @JsonInclude controls whether null or empty values are serialized. These annotations live on the POJO and document the JSON contract directly alongside the data model.
import com.fasterxml.jackson.annotation.*;
// ── @JsonProperty: rename JSON key ────────────────────────────
// Java field "userId" → JSON key "user_id"
public class UserDto {
@JsonProperty("user_id")
private int userId;
@JsonProperty("full_name")
private String fullName;
// @JsonProperty on getter also works
@JsonProperty("email_address")
public String getEmail() { return email; }
private String email;
}
// Serializes to: {"user_id":1,"full_name":"Alice","email_address":"alice@example.com"}
// ── @JsonIgnore: exclude a field ──────────────────────────────
public class UserWithPassword {
private String username;
@JsonIgnore
private String passwordHash; // excluded from serialization and deserialization
}
// ── @JsonInclude: skip null or empty fields ───────────────────
@JsonInclude(JsonInclude.Include.NON_NULL) // skip null fields
@JsonInclude(JsonInclude.Include.NON_EMPTY) // skip null, "", [], {}
public class ApiResponse {
private String status;
private String errorMessage; // omitted when null
private List<String> warnings; // omitted when null or empty
}
// ── @JsonIgnoreProperties: tolerate unknown JSON keys ─────────
@JsonIgnoreProperties(ignoreUnknown = true)
public class ForwardCompatibleDto {
private String id;
private String name;
// extra JSON keys from future API versions are silently ignored
}
// ── @JsonAlias: accept multiple JSON key names ─────────────────
public class FlexibleDto {
@JsonAlias({"username", "user_name", "login"})
private String username;
// accepts "username", "user_name", or "login" during deserialization
// serializes as "username" (primary name)
}
// ── @JsonUnwrapped: inline a nested object ─────────────────────
public class Address {
private String street;
private String city;
}
public class UserWithAddress {
private String name;
@JsonUnwrapped
private Address address; // inlined: {"name":"Alice","street":"...","city":"..."}
// without @JsonUnwrapped: {"name":"Alice","address":{"street":"...","city":"..."}}
}
// ── @JsonRootName: wrap in a root object ──────────────────────
// Requires mapper.enable(SerializationFeature.WRAP_ROOT_VALUE)
@JsonRootName("user")
public class WrappedUser {
private String name;
}
// Serializes to: {"user":{"name":"Alice"}}@JsonProperty is the most commonly used annotation — it bridges Java's camelCase naming convention with JSON APIs that use snake_case. An alternative is configuring PropertyNamingStrategies.SNAKE_CASE on the ObjectMapper globally (mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)), which converts all fields automatically without per-field annotations. Use per-field @JsonProperty when only some fields need renaming or when the renaming is irregular; use the global strategy when the entire API uses a consistent naming convention.
Custom Serializers and Deserializers
Custom serializers and deserializers give full control over how a type is converted to/from JSON — essential for types that Jackson cannot handle automatically: LocalDate/LocalDateTime, polymorphic hierarchies, third-party types, and domain types with complex JSON representations. Implement JsonSerializer<T> or JsonDeserializer<T> and register via annotations or a SimpleModule.
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.io.IOException;
// ── Custom serializer: LocalDate → ISO 8601 string ────────────
public class LocalDateSerializer extends JsonSerializer<LocalDate> {
private static final DateTimeFormatter FMT = DateTimeFormatter.ISO_LOCAL_DATE;
@Override
public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
gen.writeString(value.format(FMT));
// Writes: "2026-05-20"
}
}
// ── Custom deserializer: ISO 8601 string → LocalDate ──────────
public class LocalDateDeserializer extends JsonDeserializer<LocalDate> {
@Override
public LocalDate deserialize(JsonParser p, DeserializationContext ctx)
throws IOException {
return LocalDate.parse(p.getText(), DateTimeFormatter.ISO_LOCAL_DATE);
}
}
// ── Register via annotations (per-field) ─────────────────────
public class Event {
private String title;
@JsonSerialize(using = LocalDateSerializer.class)
@JsonDeserialize(using = LocalDateDeserializer.class)
private LocalDate date;
}
// ── Register via SimpleModule (globally for a type) ───────────
SimpleModule module = new SimpleModule();
module.addSerializer(LocalDate.class, new LocalDateSerializer());
module.addDeserializer(LocalDate.class, new LocalDateDeserializer());
ObjectMapper mapper = new ObjectMapper().registerModule(module);
// ── Alternative: jackson-datatype-jsr310 module ───────────────
// Add dependency: com.fasterxml.jackson.datatype:jackson-datatype-jsr310
// Handles all java.time types automatically
ObjectMapper mapperWithJsr310 = new ObjectMapper()
.registerModule(new com.fasterxml.jackson.datatype.jsr310.JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// ── Polymorphic types with @JsonTypeInfo + @JsonSubTypes ──────
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "dog"),
@JsonSubTypes.Type(value = Cat.class, name = "cat")
})
public abstract class Animal {
private String name;
}
public class Dog extends Animal {
private String breed;
}
public class Cat extends Animal {
private boolean indoor;
}
// Serializes Dog as: {"type":"dog","name":"Rex","breed":"Labrador"}
// Deserializes back to Dog.class based on the "type" discriminator fieldThe jackson-datatype-jsr310 module (part of the jackson-modules-java8 parent POM) is the recommended way to handle java.time types — it implements all the serializers/deserializers for LocalDate, LocalDateTime, ZonedDateTime, Duration, and others. Spring Boot auto-registers this module when it is on the classpath, so no explicit registration is needed in Spring Boot projects. Always disable WRITE_DATES_AS_TIMESTAMPS to get ISO 8601 strings instead of Unix epoch arrays.
JsonNode: Dynamic JSON Without POJOs
JsonNode is Jackson's tree model for dynamic, schema-less JSON — use it when the JSON structure is unknown at compile time, varies between requests, or when you need to inspect JSON before deciding how to process it. ObjectMapper.readTree() parses JSON into a JsonNode tree without requiring a target POJO class.
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
ObjectMapper mapper = new ObjectMapper();
String json = "{\"id\":1,\"name\":\"Alice\",\"tags\":[\"admin\",\"editor\"],\"address\":null}";
// ── Parsing to JsonNode ───────────────────────────────────────
JsonNode root = mapper.readTree(json);
// ── get() vs path() ───────────────────────────────────────────
// get("key") → returns null if key is missing (NullPointerException on chaining!)
// path("key") → returns MissingNode if missing (safe to chain, never NPE)
String name = root.get("name").asText(); // "Alice"
String missing = root.path("missing").asText(); // "" (empty string, no NPE)
int missingInt = root.path("missing").asInt(); // 0 (default)
// ── Type-safe value extraction ────────────────────────────────
int id = root.get("id").asInt();
boolean isAdmin = root.path("active").asBoolean(); // false if missing
double score = root.path("score").asDouble(); // 0.0 if missing
// ── Array iteration ───────────────────────────────────────────
JsonNode tagsNode = root.get("tags");
if (tagsNode != null && tagsNode.isArray()) {
for (JsonNode tag : tagsNode) {
System.out.println(tag.asText()); // "admin", "editor"
}
}
// ── Build JSON programmatically with ObjectNode ───────────────
ObjectNode response = mapper.createObjectNode();
response.put("status", "ok");
response.put("code", 200);
response.putNull("error");
ArrayNode items = response.putArray("items");
items.add("item1").add("item2");
// {"status":"ok","code":200,"error":null,"items":["item1","item2"]}
String responseJson = mapper.writeValueAsString(response);
// ── JsonNode.deepCopy() ───────────────────────────────────────
// JsonNode is mutable — deepCopy() before modifying to avoid side effects
ObjectNode copy = ((ObjectNode) root).deepCopy();
copy.put("name", "Bob"); // original root is unchanged
// ── treeToValue(): convert JsonNode to POJO ───────────────────
JsonNode userNode = mapper.readTree("{\"id\":1,\"name\":\"Alice\"}");
User user = mapper.treeToValue(userNode, User.class);
// ── valueToTree(): convert POJO to JsonNode ───────────────────
User newUser = new User(1, "Alice");
JsonNode node = mapper.valueToTree(newUser);Always prefer path() over get() when traversing JSON of uncertain shape — path() returns a MissingNode (never null) for absent keys, so chaining like root.path("address").path("city").asText() returns an empty string instead of throwing NullPointerException. Use has("key") to check for key existence and isNull() to distinguish JSON null from a missing key. JsonNode is especially useful in Spring Boot @RestController methods with ResponseEntity<JsonNode> return types for dynamic JSON proxy endpoints.
Java Records and Immutable POJOs with Jackson
Java records (introduced in Java 16, finalized in Java 17) are immutable data carriers with a concise syntax. Jackson 2.12+ supports records for serialization; Jackson 2.14+ adds automatic deserialization support via the canonical constructor without requiring annotations. For older Jackson versions or for controlling JSON key names, use @JsonCreator and @JsonProperty on the canonical constructor.
// ── Java Record (Jackson 2.14+ — no annotations needed) ──────
// Serialization: uses accessor methods (id(), name(), email())
// Deserialization: Jackson auto-detects canonical constructor
public record User(int id, String name, String email) {}
ObjectMapper mapper = new ObjectMapper();
User user = new User(1, "Alice", "alice@example.com");
String json = mapper.writeValueAsString(user);
// {"id":1,"name":"Alice","email":"alice@example.com"}
User parsed = mapper.readValue(json, User.class);
// parsed.id() == 1, parsed.name() == "Alice"
// ── Java Record with custom JSON keys (any Jackson version) ───
public record UserDto(
@JsonProperty("user_id") int id,
@JsonProperty("full_name") String name,
@JsonProperty("email_address") String email
) {
// @JsonCreator is needed for Jackson < 2.14
@JsonCreator
public UserDto(
@JsonProperty("user_id") int id,
@JsonProperty("full_name") String name,
@JsonProperty("email_address") String email
) {
this(id, name, email);
}
}
// Serializes to: {"user_id":1,"full_name":"Alice","email_address":"alice@example.com"}
// ── Immutable POJO (class-based, not record) ───────────────────
// Builder pattern with @JsonDeserialize(builder = ...)
@JsonDeserialize(builder = Product.Builder.class)
public final class Product {
private final String sku;
private final String name;
private final double price;
private Product(Builder b) {
this.sku = b.sku;
this.name = b.name;
this.price = b.price;
}
public String getSku() { return sku; }
public String getName() { return name; }
public double getPrice() { return price; }
@JsonPOJOBuilder(withPrefix = "") // builder methods named "sku()", not "withSku()"
public static class Builder {
private String sku;
private String name;
private double price;
public Builder sku(String sku) { this.sku = sku; return this; }
public Builder name(String name) { this.name = name; return this; }
public Builder price(double price) { this.price = price; return this; }
public Product build() { return new Product(this); }
}
}
// ── All-args constructor with @JsonCreator ────────────────────
public final class Money {
private final long amount;
private final String currency;
@JsonCreator
public Money(
@JsonProperty("amount") long amount,
@JsonProperty("currency") String currency
) {
this.amount = amount;
this.currency = currency;
}
public long getAmount() { return amount; }
public String getCurrency() { return currency; }
}Jackson 2.14 added MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS and improved record detection, making records work without any annotations. Verify your Jackson version in pom.xml — Spring Boot 3.x ships with Jackson 2.14+, so records work out of the box. For older Spring Boot 2.x projects (Jackson 2.13), add the @JsonCreator/@JsonProperty annotations to the canonical constructor. Records with @JsonIgnore on a component exclude that component from both serialization and deserialization.
Spring Boot JSON Auto-Configuration
Spring Boot auto-configures a singleton ObjectMapper bean via JacksonAutoConfiguration, pre-configured with sensible defaults. @RestController methods returning POJOs are automatically serialized to JSON; @RequestBody parameters are automatically deserialized. Customize the auto-configured mapper via application.properties or a Jackson2ObjectMapperBuilderCustomizer bean — avoid replacing the entire ObjectMapper bean unless you need full control.
# ── application.properties: Spring Boot Jackson configuration ──
# Pretty print all JSON responses
spring.jackson.serialization.indent-output=true
# Omit null fields globally
spring.jackson.default-property-inclusion=non_null
# Ignore unknown JSON fields on deserialization
spring.jackson.deserialization.fail-on-unknown-properties=false
# ISO 8601 dates instead of Unix timestamps
spring.jackson.serialization.write-dates-as-timestamps=false
spring.jackson.date-format=yyyy-MM-dd'T'HH:mm:ss
# Snake_case JSON keys for all fields
spring.jackson.property-naming-strategy=SNAKE_CASE
# ── @RestController: automatic JSON serialization ──────────────import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import com.fasterxml.jackson.databind.JsonNode;
@RestController
@RequestMapping("/api/users")
public class UserController {
// Return POJO → auto-serialized to JSON by Jackson
@GetMapping("/{id}")
public User getUser(@PathVariable int id) {
return userService.findById(id);
// Response: {"id":1,"name":"Alice","email":"alice@example.com"}
}
// Accept JSON body → auto-deserialized by Jackson
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User created = userService.create(user);
return ResponseEntity.status(201).body(created);
}
// Dynamic JSON response with JsonNode
@GetMapping("/{id}/raw")
public ResponseEntity<JsonNode> getRaw(@PathVariable int id) {
JsonNode node = userService.getRawJson(id);
return ResponseEntity.ok(node);
}
}
// ── Customize auto-configured ObjectMapper (preferred approach) ─
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
@Configuration
public class JacksonConfig {
// Customizes the auto-configured ObjectMapper without replacing it
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
return builder -> builder
.modules(new JavaTimeModule())
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.featuresToEnable(SerializationFeature.INDENT_OUTPUT);
}
// ── Full replacement (only if you need complete control) ──
// @Bean @Primary
// public ObjectMapper objectMapper() {
// return new ObjectMapper()
// .registerModule(new JavaTimeModule())
// .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// }
}The Jackson2ObjectMapperBuilderCustomizer approach is preferred over declaring an @Primary ObjectMapper bean because it extends Spring Boot's auto-configuration rather than replacing it — other auto-configurations (such as Spring MVC's message converters) continue to work correctly. Declaring a full @Primary ObjectMapper bean replaces the auto-configuration entirely and can break Spring Boot actuator endpoints and other framework components that depend on the auto-configured mapper. See the JSON performance guide for ObjectMapper benchmarking.
Jackson vs Gson vs JSON-B: Comparison
Jackson, Gson, and JSON-B are the three main Java JSON libraries. Jackson is the Spring Boot default with the richest feature set; Gson is Google's library favored for Android and simple use cases; JSON-B is the Jakarta EE standard with a specification-backed API. Choosing between them depends on your ecosystem, existing dependencies, and specific feature requirements.
// ── Jackson ───────────────────────────────────────────────────
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonProperty;
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(obj);
MyClass obj2 = mapper.readValue(json, MyClass.class);
// Null fields: INCLUDED by default
// Date format: Unix timestamp by default (configure to ISO 8601)
// ── Gson ──────────────────────────────────────────────────────
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
// Basic Gson
Gson gson = new Gson();
String gsonJson = gson.toJson(obj);
MyClass gsonObj = gson.fromJson(gsonJson, MyClass.class);
// Null fields: OMITTED by default (opposite of Jackson)
// Configured Gson
Gson configuredGson = new GsonBuilder()
.serializeNulls() // include null fields (like Jackson default)
.setPrettyPrinting() // pretty print
.setDateFormat("yyyy-MM-dd") // ISO date format
.create();
// Gson annotation — maps "user_id" JSON key to userId field
public class GsonUser {
@SerializedName("user_id")
private int userId;
// No equivalent to @JsonIgnore: use transient keyword
private transient String secret;
}
// Generic types with Gson TypeToken (equivalent to Jackson TypeReference)
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
Type listType = new TypeToken<List<User>>() {}.getType();
List<User> users = gson.fromJson(jsonArray, listType);
// ── JSON-B (Jakarta EE standard) ──────────────────────────────
// Dependency: jakarta.json.bind:jakarta.json.bind-api
// Implementation: org.eclipse.yasson:yasson (reference implementation)
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.annotation.JsonbProperty;
Jsonb jsonb = JsonbBuilder.create();
String jsonbJson = jsonb.toJson(obj);
MyClass jsonbObj = jsonb.fromJson(jsonbJson, MyClass.class);
// JSON-B annotation — equivalent to @JsonProperty
public class JsonbUser {
@JsonbProperty("user_id")
private int userId;
}
// ── Feature comparison matrix ─────────────────────────────────
// Feature Jackson Gson JSON-B
// Null fields default included omitted included
// Date default timestamp locale string ISO 8601
// Generic types TypeReference TypeToken Type parameter
// Spring Boot default YES no no
// Android-friendly moderate YES no
// Streaming API YES YES YES
// Polymorphism support @JsonTypeInfo RuntimeTypeAdapterFactory @JsonbTypeInfo
// Record support (Java 17) 2.14+ 1.10+ 2.0+Migrating from Gson to Jackson is straightforward for simple POJOs — replace @SerializedName with @JsonProperty, replace TypeToken with TypeReference, and replace GsonBuilder configuration with ObjectMapper configuration. The main behavioral difference to account for is null handling: Gson omits null fields by default while Jackson includes them — add @JsonInclude(JsonInclude.Include.NON_NULL) globally on your DTOs or configure mapper.setDefaultPropertyInclusion(Include.NON_NULL) to match Gson's behavior. See also the JSON data validation guide for schema-based validation with Jackson.
Key Terms
- ObjectMapper
- The central Jackson class for JSON serialization and deserialization.
ObjectMapperis thread-safe and should be instantiated once as a singleton — it builds internal caches for type introspection, serializer/deserializer lookups, and field access on first use, which makes subsequent operations significantly faster. Creating a newObjectMapperper request adds ~5 ms of overhead from cache rebuild. Spring Boot auto-configures a singletonObjectMapperbean. Configure the mapper with features (SerializationFeature,DeserializationFeature), modules (JavaTimeModule), and property naming strategies. The main methods arereadValue()for deserialization andwriteValueAsString()/writeValue()for serialization. - POJO
- Plain Old Java Object — a Java class with no special base class or interface requirements, following the JavaBean convention of private fields with public getters and setters and a no-argument constructor. Jackson serializes POJOs by inspecting their public getter methods (by default), mapping getter name to JSON key (stripping the
getprefix and lowercasing the first letter). Jackson deserializes POJOs using the no-arg constructor followed by setter calls. Deviating from JavaBean conventions — fields without getters, final fields, no-arg constructor absent — requires Jackson annotations (@JsonProperty,@JsonCreator) orObjectMapperconfiguration to work correctly. - JsonNode
- Jackson's tree model representation of a JSON value — a type hierarchy where
ObjectNoderepresents JSON objects,ArrayNoderepresents arrays,TextNode/IntNode/BooleanNode/NullNode/MissingNoderepresent scalar values and special states. Parse JSON to aJsonNodetree withObjectMapper.readTree(). Access fields safely withpath()(returnsMissingNodefor absent keys, never null) rather thanget()(returns null for absent keys). Extract values with type-safeasText(),asInt(),asBoolean(),asDouble(). Build JSON trees programmatically withObjectMapper.createObjectNode()andcreateArrayNode(). - @JsonProperty
- A Jackson annotation that maps a Java field, getter, or constructor parameter to a specific JSON key name. Place
@JsonProperty("json_key")on a field or getter to control both serialization (the JSON key written to the output) and deserialization (the JSON key read from the input). Without@JsonProperty, Jackson derives the JSON key from the getter name by stripping thegetprefix and lowercasing the first character —getUserId()maps touserId.@JsonPropertyis required on constructor parameters when using@JsonCreatorfor immutable POJOs and Java records, to tell Jackson which JSON key maps to which constructor argument. - TypeReference
- A Jackson abstract class used to capture generic type information at runtime, working around Java's type erasure. Type erasure means that at runtime,
List<User>andList<Product>are both justList— passingList.classtoreadValue()loses the element type.TypeReferenceuses an anonymous subclass pattern to preserve the generic type in the class's superclass type signature, which survives erasure:new TypeReference<List<User>>(). The equivalent in Gson isTypeToken. UseTypeReferencewhenever deserializing to a generic type such asList<T>,Map<String, T>, or nested generics. - PropertyNamingStrategy
- An
ObjectMapperconfiguration that globally transforms Java property names into JSON key names during serialization and deserialization. The most common strategy isPropertyNamingStrategies.SNAKE_CASE, which convertsuserIdtouser_idandfirstNametofirst_namefor all fields on all POJOs processed by that mapper. Other built-in strategies includeUPPER_CAMEL_CASE,LOWER_CASE,KEBAB_CASE, andLOWER_DOT_CASE. Configure withmapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)or in Spring Boot withspring.jackson.property-naming-strategy=SNAKE_CASE. Per-field@JsonPropertyannotations override the global strategy for individual fields.
FAQ
How do I serialize a Java object to JSON with Jackson?
Use ObjectMapper.writeValueAsString(obj) to serialize a Java object to a JSON string. The object must follow JavaBean conventions (public getters) or have Jackson annotations. For file output use writeValue(File, obj); for pretty-printed output use mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj). Jackson serializes all fields with public getters by default — fields without getters are ignored unless you configure ObjectMapper with FIELD visibility or annotate with @JsonProperty. Null fields are included by default; use @JsonInclude(JsonInclude.Include.NON_NULL) on the class to skip them. Create ObjectMapper once as a singleton — it is thread-safe and expensive to instantiate (~5 ms); reusing it processes a 10 KB object in ~0.1 ms.
How do I deserialize JSON to a Java object with Jackson?
Use ObjectMapper.readValue(jsonString, MyClass.class) to deserialize a JSON string to a typed POJO. For generic types like List<User> or Map<String, User>, use TypeReference: readValue(json, new TypeReference<List<User>>() {}). For InputStream input (e.g., HTTP response body), use readValue(inputStream, MyClass.class). Jackson maps JSON keys to Java fields by matching key names to getter/setter names (removing "get"/"set" prefix and lowercasing the first letter). Unknown JSON fields cause a JsonMappingException by default — add @JsonIgnoreProperties(ignoreUnknown = true) on the class or set objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) to ignore them.
What are the most important Jackson annotations?
@JsonProperty("name") maps a Java field or method to a different JSON key name — use it when your JSON key uses snake_case and your Java field uses camelCase. @JsonIgnore excludes a field from both serialization and deserialization. @JsonInclude(JsonInclude.Include.NON_NULL) omits null fields during serialization. @JsonIgnoreProperties(ignoreUnknown = true) on a class makes deserialization tolerant of extra JSON keys. @JsonAlias accepts multiple JSON key names during deserialization. @JsonSerialize(using = ...) and @JsonDeserialize(using = ...) attach custom serializer/deserializer classes. @JsonCreator marks a constructor or factory method as the deserialization entry point — required for immutable classes and Java records without a no-arg constructor.
How do I handle unknown JSON fields in Jackson?
Jackson throws JsonMappingException for unknown fields by default (FAIL_ON_UNKNOWN_PROPERTIES is true). There are three ways to disable this: (1) Annotate the POJO class with @JsonIgnoreProperties(ignoreUnknown = true) — the recommended approach for forward compatibility, applied per-class. (2) Configure the ObjectMapper globally: objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) — affects all deserialization on that mapper instance. (3) In Spring Boot, set spring.jackson.deserialization.fail-on-unknown-properties=false in application.properties. The per-class annotation approach is preferred because it documents the intent alongside the data model and avoids accidentally masking errors in other classes.
What is JsonNode in Jackson and when should I use it?
JsonNode is Jackson's dynamic JSON tree model — a type-safe alternative to Map<String, Object> for working with JSON of unknown or varying structure. Use ObjectMapper.readTree(jsonString) to parse JSON into a JsonNode tree. Access fields with jsonNode.path("fieldName") (returns MissingNode for missing fields — safe for chaining) rather than get() (returns null — risks NullPointerException). Read values with .asText(), .asInt(), .asBoolean(). Use JsonNode when the JSON schema is unknown at compile time, when you need to inspect JSON before deciding which POJO to deserialize into, or for dynamic JSON proxy endpoints. For known schemas, prefer typed POJOs — they are faster and catch errors at compile time.
How do I configure Jackson in Spring Boot?
Spring Boot auto-configures a singleton ObjectMapper bean via JacksonAutoConfiguration. Customize it without replacing it by setting spring.jackson.* properties in application.properties: spring.jackson.serialization.indent-output=true for pretty printing, spring.jackson.default-property-inclusion=non_null to skip null fields, spring.jackson.deserialization.fail-on-unknown-properties=false to ignore unknown fields, and spring.jackson.serialization.write-dates-as-timestamps=false for ISO 8601 dates. For programmatic customization, declare a Jackson2ObjectMapperBuilderCustomizer bean. In @RestController methods, returning a POJO auto-serializes it to JSON; @RequestBody on a parameter auto-deserializes the incoming JSON body.
How do I serialize Java records to JSON?
Java records are supported natively by Jackson 2.12+ for serialization and Jackson 2.14+ for deserialization without annotations — Jackson detects accessor methods (which match component names exactly, without "get" prefix) and the canonical constructor automatically. For Jackson versions before 2.14, add @JsonCreator on the canonical constructor and @JsonProperty on each parameter. Spring Boot 3.x ships with Jackson 2.14+, so records work out of the box. Records are immutable — Jackson uses accessor methods for serialization and the canonical constructor for deserialization (no setters involved). To control JSON key names, add @JsonProperty("json_key") to the constructor parameter. @JsonIgnore on a record component excludes it from both serialization and deserialization.
What is the difference between Jackson and Gson for Java JSON?
Jackson and Gson have different defaults: null handling — Jackson includes null fields by default; Gson omits them (configure with GsonBuilder.serializeNulls()). Date serialization — Jackson outputs Unix timestamps; Gson outputs locale-specific strings — both configurable to ISO 8601. Performance — Jackson is generally faster for large objects due to its streaming API and bytecode optimization. Annotations — Jackson uses @JsonProperty/@JsonIgnore; Gson uses @SerializedName/transient — not interchangeable. Generic types — Jackson uses TypeReference; Gson uses TypeToken. Spring Boot integration — Jackson is the default and auto-configured; Gson requires replacing the auto-configuration. JSON-B (Jakarta EE standard) uses @JsonbProperty and the Yasson reference implementation, with a specification-backed API suited for Jakarta EE environments.
Further reading and primary sources
- Jackson Databind GitHub — Jackson databind source, issues, and full annotation reference
- Jackson Documentation: ObjectMapper — Official Javadoc for ObjectMapper methods and configuration
- Spring Boot: Customize Jackson ObjectMapper — Spring Boot guide for customizing Jackson auto-configuration
- Jackson datatype-jsr310 Module — Jackson module for java.time types — LocalDate, LocalDateTime, ZonedDateTime
- Gson User Guide — Google Gson serialization, deserialization, and GsonBuilder configuration