Unravel › Java concepts › APIs and web
Custom Jackson serialization and modules
When default JSON mapping is not enough (dates in a special format, money as a string, hiding fields per audience, circular references), Jackson can be tuned with annotations, features and custom serializers registered in modules.
Learn these first
At a glance
A translator with house style rules.
- 1Global behaviour is set on the ObjectMapper: registerModule(new JavaTimeModule()) for java.time, disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) for ISO dates, disable(FAIL_ON_UNKNOWN_PROPERTIES) to tolerate new fields from a producer
- 2In Spring Boot, define properties (spring.jackson.*) or a Jackson2ObjectMapperBuilderCustomizer instead of creating your own mapper, so MVC, WebClient and messaging share it
- 3A custom JsonSerializer<T> writes a value with gen.writeString(...); a JsonDeserializer<T> reads from the JsonParserAttach it with @JsonSerialize(using = ...) or register it in a SimpleModule.
- 4@JsonView exposes different field sets to different audiences; @JsonIgnoreProperties, @JsonIdentityInfo and @JsonManagedReference tame cycles between parent and child
- Never accept polymorphic types with default typing from untrusted JSON (enableDefaultTyping, @JsonTypeInfo(use = CLASS)): it can instantiate arbitrary classes, a known remote code execution risk
- Money and large numbers: JSON numbers become double in JavaScript
- Jackson builds a serializer or deserializer per type lazily, using reflection on getters, fields and constructors guided by annotations, and caches the result
public class MoneySerializer extends JsonSerializer<BigDecimal> { @Override public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeString(value.setScale(2, RoundingMode.HALF_UP).toPlainString()); }}public class Invoice { @JsonSerialize(using = MoneySerializer.class) private BigDecimal total;}- line 1 uses JsonSerializer, the core of Custom Jackson serialization and modules
- line 3 uses JsonGenerator, the core of Custom Jackson serialization and modules
- line 9 uses JsonSerialize, the core of Custom Jackson serialization and modules
Read the full explanation (about 1 min)
When default JSON mapping is not enough (dates in a special format, money as a string, hiding fields per audience, circular references), Jackson can be tuned with annotations, features and custom serializers registered in modules.
A mental model
A translator with house style rules. By default she translates word for word; for special terms you hand her a glossary (serializers) and a style sheet (features such as date format and naming).
How it works
Global behaviour is set on the ObjectMapper: registerModule(new JavaTimeModule()) for java.time, disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) for ISO dates, disable(FAIL_ON_UNKNOWN_PROPERTIES) to tolerate new fields from a producer.
In Spring Boot, define properties (spring.jackson.*) or a Jackson2ObjectMapperBuilderCustomizer instead of creating your own mapper, so MVC, WebClient and messaging share it.
A custom JsonSerializer<T> writes a value with gen.writeString(...); a JsonDeserializer<T> reads from the JsonParser. Attach it with @JsonSerialize(using = ...) or register it in a SimpleModule.
@JsonView exposes different field sets to different audiences; @JsonIgnoreProperties, @JsonIdentityInfo and @JsonManagedReference tame cycles between parent and child.
Going deeper
Never accept polymorphic types with default typing from untrusted JSON (enableDefaultTyping, @JsonTypeInfo(use = CLASS)): it can instantiate arbitrary classes, a known remote code execution risk. Restrict subtypes to an allow-list.
Money and large numbers: JSON numbers become double in JavaScript. Serialize BigDecimal and 64-bit ids as strings if a browser consumes them.
ObjectMapper is thread safe after configuration and expensive to build; share one instance. Changing its configuration after first use is not safe.
Under the hood
Jackson builds a serializer or deserializer per type lazily, using reflection on getters, fields and constructors guided by annotations, and caches the result. Streaming JsonGenerator and JsonParser are the low-level tokens beneath, and are much faster and lighter on memory for huge documents.
Common mistakes
- Default typing on untrusted input.
- A new ObjectMapper per call.
- Dates written as numeric timestamps by accident.
- Bidirectional relations without cycle handling, causing stack overflow.
How Unravel recognises it
When you paste code, Unravel looks for these signs of custom jackson serialization and modules. Spotting them yourself is a good habit when you read unfamiliar code:
- Annotations:
@JsonSerialize@JsonDeserialize@JsonView@JsonAnyGetter@JsonAnySetter@JsonUnwrapped@JsonNaming@JsonPropertyOrder@JsonAlias@JsonValue@JsonManagedReference@JsonBackReference@JsonIdentityInfo - Types:
JsonSerializerJsonDeserializerSimpleModuleStdSerializerStdDeserializerJavaTimeModuleJackson2ObjectMapperBuilderDeserializationFeatureSerializationFeaturePropertyNamingStrategiesJsonGeneratorJsonParserSerializerProviderDeserializationContextand 2 more
Part of a learning path
- Building and documenting APIs · previous: OpenAPI and Swagger documentation · next: GraphQL servers
Related concepts
Frequently asked questions
What is Custom Jackson serialization and modules?
When default JSON mapping is not enough (dates in a special format, money as a string, hiding fields per audience, circular references), Jackson can be tuned with annotations, features and custom serializers registered in modules.
What are the most common mistakes with Custom Jackson serialization and modules?
Default typing on untrusted input. A new ObjectMapper per call. Dates written as numeric timestamps by accident. Bidirectional relations without cycle handling, causing stack overflow.
How can I see Custom Jackson serialization and modules in real code?
Paste your own Java into Unravel and it will point out where custom jackson serialization and modules appears, explain how it works at the detail level you choose, and draw the flow of the code around it.