What's the correct way to customize the ObjectMapper in Spring Boot, and what's the pitfall of defining your own ObjectMapper bean?
answer
- Ladder: properties -> customizer -> Module bean -> replace
- Jackson2ObjectMapperBuilderCustomizer keeps Boot defaults
- Own @Bean ObjectMapper => auto-config backs off (ConditionalOnMissingBean)
- Rebuild via builder.build() to retain defaults
- Never new ObjectMapper() in a service; inject the bean
basics
~10 sPrefer a Jackson2ObjectMapperBuilderCustomizer bean or spring.jackson.* properties, which tweak the auto-configured mapper while keeping Boot's defaults. Defining your own @Bean ObjectMapper makes auto-config back off, so you lose those defaults.
solid answer
~40 sThere's a hierarchy of customization. First choice: spring.jackson.* properties for simple toggles. Second: register a Jackson2ObjectMapperBuilderCustomizer bean to add modules, serializers, or feature flags programmatically — Spring applies it to the same builder that produces the auto-configured mapper, so all Boot defaults (ISO dates, module auto-registration, property binding) are preserved. Third: expose a Spring bean implementing com.fasterxml.jackson.databind.Module and it's auto-registered. The pitfall: if you declare your own @Bean ObjectMapper, JacksonAutoConfiguration backs off (it's @ConditionalOnMissingBean), so you get a bare mapper missing Boot's defaults — java.time may break, spring.jackson.* stops applying to it. If you truly need a fresh mapper, build it via Jackson2ObjectMapperBuilder to retain the defaults. Also remember there can be two mappers: the web one and any you build for internal use.
code
java · 20 lines@Configuration
class JacksonConfig {
// PREFERRED: layer changes onto Boot's builder, keep all defaults
@Bean
Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
return builder -> {
builder.featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
builder.serializerByType(Money.class, new MoneySerializer());
builder.propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
};
}
// If you MUST own the bean, build via the Spring builder (defaults preserved).
// Doing `new ObjectMapper()` here would lose JavaTimeModule + spring.jackson.*.
// @Bean
// ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
// return builder.build();
// }
}go deeper
Know spring.jackson.* exists and that you shouldn't randomly new-up an ObjectMapper.
Use Jackson2ObjectMapperBuilderCustomizer for custom serializers/modules; know properties for toggles.
Explain the ConditionalOnMissingBean back-off, defaults lost when replacing the bean, and how to rebuild via the builder.
Design a customization policy, decide web-only vs global mapper overrides, and prevent per-instance mapper creation across the codebase.
## The customization ladder (least to most invasive) ### 1. `spring.jackson.*` properties Declarative toggles for `SerializationFeature`, `DeserializationFeature`, `MapperFeature`, inclusion, naming strategy, timezone, etc. These bind into the auto-configured `Jackson2ObjectMapperBuilder`, so defaults are preserved. Use for simple, static configuration. ### 2. `Jackson2ObjectMapperBuilderCustomizer` bean (the idiomatic hook) ```java @Configuration class JacksonConfig { @Bean Jackson2ObjectMapperBuilderCustomizer customizer() { return builder -> { builder.featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS); builder.serializerByType(Money.class, new MoneySerializer()); builder.modules(new MyDomainModule()); builder.propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); }; } } ``` Spring applies **all** `Jackson2ObjectMapperBuilderCustomizer` beans to the same builder that creates the shared `ObjectMapper`. This keeps every Boot default and layers your changes on top. Use it when properties can't express the change (custom serializers/deserializers, conditional logic, custom modules). Order multiple customizers with `@Order`. ### 3. A `Module` bean Any bean of type `com.fasterxml.jackson.databind.Module` is auto-detected and registered (Boot passes `Module` beans to the builder). Good for packaging a cohesive set of serializers. ### 4. Replacing the mapper entirely (the pitfall) `JacksonAutoConfiguration`'s `ObjectMapper` bean is `@ConditionalOnMissingBean`. If you declare: ```java @Bean ObjectMapper objectMapper() { return new ObjectMapper(); } ``` auto-configuration **backs off**. Consequences: - No `JavaTimeModule` auto-registered -> `java.time` serialization may fail or emit timestamps. - `WRITE_DATES_AS_TIMESTAMPS` is back to Jackson's default (enabled). - `spring.jackson.*` properties no longer apply to this mapper (they configured the builder, which you bypassed). - Naming strategy, inclusion, etc. revert to raw defaults. If you must own the bean, build it through the Spring-provided builder to keep defaults: ```java @Bean ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) { return builder.build(); // Boot defaults preserved, then customize } ``` ## Two-mapper reality The web-layer mapper (used by message converters) and a mapper you `@Autowired` for internal serialization can be the same shared bean — but if you `new ObjectMapper()` inside a service, that instance is unconfigured and independent. Inject the shared bean instead of instantiating. ## Overriding just the web converter If you want different Jackson behavior only for HTTP (not internal usage), you can register a custom `MappingJackson2HttpMessageConverter` via `WebMvcConfigurer.configureMessageConverters`/`extendMessageConverters` with its own mapper, leaving the shared bean intact. ## Gotchas - Multiple customizers all run; don't assume yours is the only one. - `builder.build()` produces a **new** mapper each call — reuse the bean, don't rebuild per request (expensive; mappers are thread-safe and meant to be shared). - Immutability: mutating a shared `ObjectMapper` at runtime is discouraged; configure at startup. - In tests, `@JsonTest` gives you the configured mapper; don't hand-roll one. ## When to use what - Static flags -> properties. - Custom serializers/modules/logic while keeping defaults -> `Jackson2ObjectMapperBuilderCustomizer`. - Full control (rare) -> build via `Jackson2ObjectMapperBuilder`, never a bare `new ObjectMapper()`.
- Why does '@Bean ObjectMapper objectMapper() { return new ObjectMapper(); }' break java.time serialization?Because JacksonAutoConfiguration is @ConditionalOnMissingBean — your bean makes it back off, so JavaTimeModule isn't auto-registered and WRITE_DATES_AS_TIMESTAMPS reverts to enabled. Build via Jackson2ObjectMapperBuilder instead to keep those defaults.
- How do you add a custom serializer while keeping all Spring Boot defaults?Register a Jackson2ObjectMapperBuilderCustomizer bean and call builder.serializerByType(...) (or contribute a Module bean). Spring applies it to the same builder that produces the auto-configured mapper, preserving defaults.
saying these in an interview costs you the question
- Recommending 'new ObjectMapper()' as the way to customize in Boot
- Not knowing the @ConditionalOnMissingBean back-off behavior
- Instantiating ObjectMapper inside services instead of injecting the shared bean
- Thinking spring.jackson.* still applies after you replace the mapper bean