Key Takeaways
- Jackson ObjectMapper converts Java values to JSON with
writeValueAsString()and JSON back to Java withreadValue(). - Mapping depends on discovered properties, constructors, annotations, modules, and target types—not simply on copying fields.
- Configure your mapper before using it, then reuse it. Preserve generic element types with
TypeReferenceorJavaType. - Jackson 2 and Jackson 3 have different packages, configuration APIs, and defaults. Match examples to your application's dependency line.
Jackson ObjectMapper is the data-binding class that translates between Java objects and JSON. It belongs to jackson-databind, which builds on Jackson's streaming parser and generator APIs. Annotations let you customize names, ignored properties, and object construction without writing the conversion yourself.
// Fragment: objectMapper is configured and Person is defined below.
// These calls belong inside a method that handles JsonProcessingException.
String json = objectMapper.writeValueAsString(new Person("Sam", "Erickson"));
Person person = objectMapper.readValue(json, Person.class);This is useful for HTTP request and response bodies, configuration files, and messages. The official databind guide introduces the same object-to-JSON workflow.
What Is Serialization?
Serialization here means converting a Java value into a JSON representation; deserialization means constructing a Java value from JSON. writeValueAsString() returns JSON text as a Java String, while writeValueAsBytes() produces a byte array.
This isn't Java's native object serialization, which uses its own object-stream format. Jackson JSON mapping doesn't require your model to implement Serializable. See our separate guide to Java serialization for that distinction.
Jackson 2 and Jackson 3: Choose the Right Setup
The executable examples below use Jackson 2.21.7, released on September 21, 2026, from the supported 2.21 LTS line. This keeps them directly applicable to applications using com.fasterxml.jackson. The patch release notes document the pinned release.
| Concern | Jackson 2 | Jackson 3 |
|---|---|---|
| Databind dependency | com.fasterxml.jackson.core:jackson-databind | tools.jackson.core:jackson-databind |
| ObjectMapper package | com.fasterxml.jackson.databind | tools.jackson.databind |
| Configuration | Builder configuration or mutable mapper configuration | Builder configuration; mapper instances are immutable |
| Java time support | Add and register JavaTimeModule | Integrated into databind |
The current databind documentation explains Jackson 3 construction, and the Jackson 3 transition notes describe compatibility changes. Jackson annotations retain the com.fasterxml.jackson.annotation package.
A Runnable Jackson ObjectMapper Example
Use a Maven project configured for Java 17 or newer. Add these dependencies inside your pom.xml's dependencies element; Maven also resolves databind's core and annotation dependencies.
<!-- Dependency fragment inside <dependencies>. -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.21.7</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.21.7</version>
</dependency>Save this as src/main/java/ObjectMapperDemo.java, import the Maven project into your IDE, and run its main() method.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.time.LocalDate;
public class ObjectMapperDemo {
public record Person(String first, String last) {}
private static final ObjectMapper MAPPER = JsonMapper.builder()
.addModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
public static void main(String[] args) {
try {
String json = MAPPER.writeValueAsString(
new Person("Sam", "Erickson"));
System.out.println(json);
Person person = MAPPER.readValue(json, Person.class);
System.out.println(person.first()); // Sam
System.out.println(person.last()); // Erickson
LocalDate date = LocalDate.of(2026, 10, 17);
String dateJson = MAPPER.writeValueAsString(date);
System.out.println(dateJson); // "2026-10-17"
System.out.println(MAPPER.readValue(dateJson, LocalDate.class));
} catch (JsonProcessingException e) {
throw new IllegalStateException("JSON mapping failed", e);
}
}
}The Person JSON represents these properties:
{"first":"Sam","last":"Erickson"}Don't make object-member order part of your application contract: JSON objects are unordered collections of name/value pairs.
The example uses a record, whose canonical constructor accepts its declared components. Jackson supports record binding without a no-argument constructor or a parameter-name module. Unlike the original field-based example, you access the values with person.first() and person.last().
Why Configure JavaTimeModule?
In Jackson 2, JavaTimeModule supplies support for java.time types. In this example, disabling SerializationFeature.WRITE_DATES_AS_TIMESTAMPS makes LocalDate serialize as an ISO-8601 string; enabling it would produce an array representation for that type.
The catch block handles the checked JsonProcessingException used by these Jackson 2 string operations. File and stream operations can also require handling IOException. At a service boundary, translate mapping failures into controlled errors rather than exposing exception details or logging sensitive request bodies.
How Does Java ObjectMapper Work?
ObjectMapper coordinates data binding: discovering properties, selecting serializers or deserializers, and applying configuration. Its JsonFactory supplies parsers and generators for the stream boundary.
Serialization:
Java value -> ObjectMapper + serializers -> JsonGenerator -> JSON
Deserialization:
JSON -> JsonParser -> ObjectMapper + deserializers -> Java value
Mapping rules: target types + properties + annotations + modules
Stream boundary: parsers/generators supplied by JsonFactorySerialization: Properties Become JSON Tokens
The data-binding layer selects serializers using type information, property discovery, annotations, and registered modules. Serializers tell the generator which JSON names and values to write; the generator doesn't independently inspect your POJO fields.
A POJO is a plain Java object. Its mapped properties can come from public fields, getters, or explicitly annotated members—not just from fields.
Deserialization: Tokens Become Constructed Objects
The parser reads JSON tokens. Deserializers interpret those tokens for the requested target type, construct values, and populate their properties. The ObjectMapper API documentation in its source describes its supported models and configuration.
Ordinary nested objects commonly work by convention, but every nested type still needs a usable construction path and compatible property mapping. Constructors, visibility rules, modules, and generic type information all matter.
Jackson Annotations
Annotations customize the JSON contract for a type or property. The official annotation guide covers renaming, ignoring, and constructor selection.
@JsonIgnore: Exclude a Property in Both Directions
With ordinary property binding, @JsonIgnore excludes the logical property from both serialization and deserialization. Here's a constructible model; add its import at the top of ObjectMapperDemo.java and the nested class inside ObjectMapperDemo.
import com.fasterxml.jackson.annotation.JsonIgnore;
// Fragment: nested model inside ObjectMapperDemo.
public static class User {
public String username;
@JsonIgnore
public String password;
public User() {}
}// Fragment: inside main's try block, using the User model above.
User user = MAPPER.readValue(
"{\"username\":\"abc\",\"password\":\"example-only\"}", User.class);
System.out.println(user.username); // abc
System.out.println(user.password); // null
user.password = "example-only";
System.out.println(MAPPER.writeValueAsString(user)); // {"username":"abc"}Jackson combines matching fields and accessors into logical properties, so annotation placement and explicit overrides matter. Avoid contradictory annotations on the same property.
@JsonProperty: Give a Property a Different JSON Name
@JsonProperty("pWord") maps the Java property named password to the JSON name pWord. Replace the previous nested User class with this alternative record:
import com.fasterxml.jackson.annotation.JsonProperty;
// Fragment: alternative nested model, replacing the previous User class.
public record User(
String username,
@JsonProperty("pWord") String password) {}// Fragment: inside main's try block, using the alternative User record.
String userJson = MAPPER.writeValueAsString(
new User("abc", "example-only"));
System.out.println(userJson); // Includes "pWord", not "password".
User user = MAPPER.readValue(userJson, User.class);
System.out.println(user.password()); // example-onlyThis demonstrates bidirectional renaming, not a recommended credential response model. Don't return passwords in real API responses.
Directional Access, Constructors, and Naming Rules
To accept a property from JSON but omit it from output, use @JsonProperty(access = JsonProperty.Access.WRITE_ONLY). READ_ONLY does the reverse: output is allowed, input assignment isn't. These directions are defined from the Java property's perspective in the JsonProperty access documentation.
For an immutable non-record class, an explicit property-based @JsonCreator constructor with @JsonProperty-named parameters makes the construction contract clear. In Jackson 2, ParameterNamesModule can supply constructor parameter names when you compile with -parameters; constructor selection must still be unambiguous or explicitly configured.
Use annotations for individual contract exceptions. For a naming convention across many types, prefer a mapper-level naming strategy; use mix-ins to associate mapping annotations with classes you don't own.
Deserialize Generic Lists with TypeReference
readValue(json, List.class) doesn't tell Jackson that the elements should be User or Person objects. Java's type erasure removes that element-type information from the raw class token. See our introduction to Java generics and type inference for the language background.
// Fragment: add these imports at the top of ObjectMapperDemo.java.
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.List;
// Fragment: inside main's try block.
String peopleJson = "[{\"first\":\"Sam\",\"last\":\"Erickson\"}]";
List<Person> people = MAPPER.readValue(
peopleJson, new TypeReference<List<Person>>() {});
System.out.println(people.get(0).first()); // SamTypeReference captures the full generic type through an anonymous subclass. For types assembled dynamically, you can instead pass a JavaType, such as one built with MAPPER.getTypeFactory().constructCollectionType(List.class, Person.class).
Reuse Your Mapper and Respect Framework Configuration
Jackson 2 ObjectMapper is thread-safe when all configuration happens before any reads or writes. Configure modules and features once, publish the mapper, and reuse it; don't change a shared mapper while requests are using it.
For supported per-operation changes, use immutable ObjectReader and ObjectWriter instances. For example, MAPPER.writerWithDefaultPrettyPrinter() creates a writer for indented output without changing the mapper's output configuration.
Spring Boot's JSON integration manages its own mapper configuration. Spring Boot 4 prefers Jackson 3 and auto-configures a JsonMapper; its Jackson 2 support is deprecated. Inject the framework-managed mapper appropriate to your stack rather than assuming new ObjectMapper() reproduces its settings. This distinction also matters when following a Spring Boot Kafka consumer JSON example.
Unknown Properties, Nulls, and Input Validation
By default, Jackson 2 fails on an unhandled unknown property; Jackson 3 changed that default to ignoring unknown properties. Choose the policy deliberately rather than relying on a major version's defaults.
@JsonIgnoreProperties(ignoreUnknown = true) permits unknown input properties for an annotated type. Prefer that local decision when integrating with an extensible external response; globally ignoring unknown properties can hide misspelled request fields.
Missing and null are different cases. With the example's Jackson 2 configuration, either a missing last property or an explicit "last": null produces a Person whose last() is null. Mapping alone doesn't establish that an input is valid for your application.
The deserialization feature documentation explains stricter creator binding: enable FAIL_ON_MISSING_CREATOR_PROPERTIES to reject missing constructor arguments, and FAIL_ON_NULL_CREATOR_PROPERTIES to reject null creator values. Both are disabled by default in Jackson 2. Apply required-field and domain validation intentionally.
Tree Processing, Streaming, and XML
Use JsonNode When the Structure Is Dynamic
readTree() produces a JsonNode tree instead of binding directly to a model. It's useful when you need to inspect or transform JSON whose shape isn't fixed. A tree still materializes the document in memory.
Use Streaming for Incremental Processing
The JsonParser and JsonGenerator APIs let you process JSON incrementally. Reading from an InputStream doesn't by itself make a resulting List<Person> memory-bounded: binding that list still creates its elements. For large inputs, process records incrementally and close owned resources.
Use XmlMapper for XML
Jackson also provides XmlMapper through jackson-dataformat-xml. It offers similar data-binding operations for XML, but XML has format-specific mapping rules and isn't simply JSON with different punctuation.
Common Mistakes
- Creating or reconfiguring a mapper for every request instead of establishing configuration ownership.
- Assuming an all-arguments constructor is enough without checking how Jackson selects it and identifies its parameters.
- Using raw collection classes when you need typed elements.
- Testing only successful round trips. Also test unknown fields, missing values, nulls, malformed input, and properties that must never appear in responses.
FAQ
How do I convert a Java object to JSON with Jackson ObjectMapper?
Call writeValueAsString(value) on a configured ObjectMapper to obtain JSON text as a String. Use writeValueAsBytes(value) for a byte array or writeValue(outputStream, value) to write to a stream.
How do I convert JSON to a Java object with ObjectMapper?
Call readValue(json, Person.class) for a concrete target type. The target needs a usable construction path, such as a record constructor, a no-argument constructor with writable properties, or an explicitly configured creator.
How do I deserialize a generic List with Jackson?
Pass a TypeReference such as new TypeReference<List<User>>() {} to readValue(), or supply a JavaType containing the element type. List.class alone doesn't preserve the User element type.
Is Jackson ObjectMapper thread-safe?
Jackson 2 ObjectMapper is thread-safe when all configuration is completed before any reads or writes; don't mutate a shared mapper afterward. Jackson 3 mapper instances are immutable, and both lines support reusable readers and writers for supported per-operation configuration.
How do I configure Jackson to handle Java date and time types?
With Jackson 2, add jackson-datatype-jsr310 and register JavaTimeModule before using the mapper. Disable SerializationFeature.WRITE_DATES_AS_TIMESTAMPS for ISO-8601 string output for types such as LocalDate; Jackson 3 integrates Java time support into databind.
Further Reading
- Jackson 2 databind guide — object binding, generic types, trees, and streaming.
- Jackson 2.21 release and support information — LTS status and patch releases.
- Jackson 3 transition notes — package, API, and default changes.
- Jackson annotations guide — property names, ignored properties, and creators.
- Jackson Java support modules — Java time and parameter-name configuration.
- Spring Boot JSON reference — framework-managed mapper configuration.
- Jackson polymorphic deserialization security guidance — risks and subtype allowlists.
- Jackson XML module — XmlMapper usage and XML-specific behavior.
