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.
| Feature | Default | Effect when enabled |
INDENT_OUTPUT | disabled | Pretty-print with indentation |
WRITE_DATES_AS_TIMESTAMPS | enabled | Write dates as epoch numbers; disable for ISO-8601 strings |
WRITE_ENUMS_USING_TO_STRING | disabled | Use toString() instead of name() for enums |
ORDER_MAP_ENTRIES_BY_KEYS | disabled | Sort map keys alphabetically in output |
WRITE_NULL_MAP_VALUES | enabled | Include map entries with null values |
FAIL_ON_EMPTY_BEANS | enabled | Throw if serialising a class with no properties; disable to write {} |
WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED | disabled | Write 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);
| Feature | Default | Effect when enabled |
FAIL_ON_UNKNOWN_PROPERTIES | enabled | Throw UnrecognizedPropertyException for unmapped fields; disable in APIs |
FAIL_ON_NULL_FOR_PRIMITIVES | disabled | Throw when JSON null is mapped to a primitive |
USE_BIG_DECIMAL_FOR_FLOATS | disabled | Use BigDecimal instead of double for floating-point values |
USE_BIG_INTEGER_FOR_INTS | disabled | Use BigInteger for large integer values |
ACCEPT_SINGLE_VALUE_AS_ARRAY | disabled | Allow a single value where an array is expected |
ACCEPT_EMPTY_STRING_AS_NULL_OBJECT | disabled | Treat "" as null when deserialising objects |
READ_UNKNOWN_ENUM_VALUES_AS_NULL | disabled | Return 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);
| Feature | Default | Effect when enabled |
USE_ANNOTATIONS | enabled | Process Jackson annotations; disable to ignore all annotations |
SORT_PROPERTIES_ALPHABETICALLY | disabled | Sort serialised fields alphabetically |
ACCEPT_CASE_INSENSITIVE_PROPERTIES | disabled | Match JSON fields case-insensitively to Java properties |
ACCEPT_CASE_INSENSITIVE_ENUMS | disabled | Deserialise enum values case-insensitively |
DEFAULT_VIEW_INCLUSION | enabled | Include non-view-annotated fields in all views when disabled |
USE_STATIC_TYPING | disabled | Use 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
| Strategy | Example output |
SNAKE_CASE | first_name |
UPPER_CAMEL_CASE | FirstName |
LOWER_CASE | firstname |
KEBAB_CASE | first-name |
LOWER_DOT_CASE | first.name |