ObjectMapper is thread-safe after configuration is complete. Create one shared instance per application (or per configuration profile) and reuse it everywhere.

import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; // Create once — typically as a Spring @Bean or a static singleton public class JacksonConfig { public static final ObjectMapper MAPPER = new ObjectMapper() .registerModule(new JavaTimeModule()) .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) .enable(SerializationFeature.INDENT_OUTPUT); // pretty-print // Prevent instantiation private JacksonConfig() {} } // Use everywhere String json = JacksonConfig.MAPPER.writeValueAsString(myObject); Never call configuration methods (enable(), disable(), configure(), registerModule()) on a shared ObjectMapper after other threads start using it. These methods mutate shared state and are not thread-safe when called concurrently with reads/writes. Configure once, then share.
FeatureDefaultEffect when enabled
INDENT_OUTPUTdisabledPretty-print with indentation
WRITE_DATES_AS_TIMESTAMPSenabledWrite dates as epoch numbers; disable for ISO-8601 strings
WRITE_ENUMS_USING_TO_STRINGdisabledUse toString() instead of name() for enums
ORDER_MAP_ENTRIES_BY_KEYSdisabledSort map keys alphabetically in output
WRITE_NULL_MAP_VALUESenabledInclude map entries with null values
FAIL_ON_EMPTY_BEANSenabledThrow if serialising a class with no properties; disable to write {}
WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPEDdisabledWrite single-element arrays as plain values
ObjectMapper mapper = new ObjectMapper() .enable(SerializationFeature.INDENT_OUTPUT) .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
FeatureDefaultEffect when enabled
FAIL_ON_UNKNOWN_PROPERTIESenabledThrow UnrecognizedPropertyException for unmapped fields; disable in APIs
FAIL_ON_NULL_FOR_PRIMITIVESdisabledThrow when JSON null is mapped to a primitive
USE_BIG_DECIMAL_FOR_FLOATSdisabledUse BigDecimal instead of double for floating-point values
USE_BIG_INTEGER_FOR_INTSdisabledUse BigInteger for large integer values
ACCEPT_SINGLE_VALUE_AS_ARRAYdisabledAllow a single value where an array is expected
ACCEPT_EMPTY_STRING_AS_NULL_OBJECTdisabledTreat "" as null when deserialising objects
READ_UNKNOWN_ENUM_VALUES_AS_NULLdisabledReturn null for unrecognised enum values instead of throwing
ObjectMapper mapper = new ObjectMapper() .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) // most important for REST APIs .enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS) .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
FeatureDefaultEffect when enabled
USE_ANNOTATIONSenabledProcess Jackson annotations; disable to ignore all annotations
SORT_PROPERTIES_ALPHABETICALLYdisabledSort serialised fields alphabetically
ACCEPT_CASE_INSENSITIVE_PROPERTIESdisabledMatch JSON fields case-insensitively to Java properties
ACCEPT_CASE_INSENSITIVE_ENUMSdisabledDeserialise enum values case-insensitively
DEFAULT_VIEW_INCLUSIONenabledInclude non-view-annotated fields in all views when disabled
USE_STATIC_TYPINGdisabledUse declared (static) type, not runtime type, for serialisation
ObjectMapper mapper = new ObjectMapper() .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);

ObjectReader and ObjectWriter are lightweight, immutable views over ObjectMapper. They let you apply per-call configuration (different views, schemas, features) without mutating the shared mapper.

ObjectMapper mapper = new ObjectMapper(); // ObjectWriter — immutable; safe to share or create on demand ObjectWriter prettyWriter = mapper.writerWithDefaultPrettyPrinter(); ObjectWriter viewWriter = mapper.writerWithView(Views.Public.class); String pretty = prettyWriter.writeValueAsString(myObject); String viewJson = viewWriter.writeValueAsString(myObject); // Chain overrides ObjectWriter customWriter = mapper .writer() .with(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) .withView(Views.Admin.class) .withDefaultPrettyPrinter(); // ObjectReader — immutable; preferred for type-safe reading ObjectReader userReader = mapper.readerFor(User.class); ObjectReader listReader = mapper.readerForListOf(User.class); User user = userReader.readValue(json); List<User> users = listReader.readValue(jsonArray); // Update an existing object (merge rather than replace) User existing = new User(1, "Alice", "alice@example.com"); ObjectReader updater = mapper.readerForUpdating(existing); User merged = updater.readValue("{\"name\":\"Alicia\"}"); // only name changed System.out.println(merged.getEmail()); // alice@example.com (unchanged) readerForUpdating() is extremely useful for partial updates (HTTP PATCH): it reads only the provided fields and merges them into an existing object, leaving unspecified fields unchanged.
// copy() creates an independent copy with the same config — safe to configure further ObjectMapper apiMapper = baseMapper.copy() .disable(SerializationFeature.INDENT_OUTPUT); ObjectMapper debugMapper = baseMapper.copy() .enable(SerializationFeature.INDENT_OUTPUT); // --- JsonView: expose different fields to different callers --- class Views { interface Public {} interface Internal extends Public {} } class UserDto { @JsonView(Views.Public.class) public String name; @JsonView(Views.Public.class) public String email; @JsonView(Views.Internal.class) // only visible in Internal view public String passwordHash; } ObjectMapper mapper = new ObjectMapper(); // Public view — hides passwordHash String publicJson = mapper.writerWithView(Views.Public.class) .writeValueAsString(user); // Internal view — includes everything in Public + Internal String internalJson = mapper.writerWithView(Views.Internal.class) .writeValueAsString(user); import com.fasterxml.jackson.databind.PropertyNamingStrategies; ObjectMapper mapper = new ObjectMapper() .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); record UserProfile(String firstName, String lastName, String emailAddress) {} // Writes: {"first_name":"Alice","last_name":"Smith","email_address":"a@s.com"} String json = mapper.writeValueAsString(new UserProfile("Alice", "Smith", "a@s.com")); // Reads back correctly UserProfile p = mapper.readValue(json, UserProfile.class); System.out.println(p.firstName()); // Alice
StrategyExample output
SNAKE_CASEfirst_name
UPPER_CAMEL_CASEFirstName
LOWER_CASEfirstname
KEBAB_CASEfirst-name
LOWER_DOT_CASEfirst.name