jackson JSON serialization & deserialization annotations β cheat sheet¶
jackson in one sentence
Jackson turns Java objects β JSON. It works out of the box, and you use annotations on your DTOs when the default JSON isnβt what you want.
jackson common annotations & patterns¶
1) @JsonProperty β rename or control access¶
Why: your Java field names donβt match the API JSON, or you want a field to be read-only/write-only in JSON.
How it affects direction
- Serialization (object β JSON): field appears using the name you give.
- Deserialization (JSON β object): field is read from that JSON name.
public class UserDto {
@JsonProperty("user_id")
private Long id;
@JsonProperty(value = "email", access = JsonProperty.Access.WRITE_ONLY)
private String email; // accepted on input but hidden in output
}
Input β Object
Object β Output
Handy flags (max 5)
value = "name"β JSON field name.access = READ_ONLYβ show in output only.access = WRITE_ONLYβ accept on input only.required = trueβ fail if missing on input.defaultValue = "..."β used if absent on input.
2) @JsonInclude β drop noise (nulls, empties, defaults)¶
Why: make responses smaller/cleaner.
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ProfileDto {
private String name;
private String bio; // null β omitted
}
Object β Output
Useful modes (pick what you need)
ALWAYSβ include everything (default).NON_NULLβ drop onlynull.NON_EMPTYβ dropnull,"",[],{}.NON_DEFAULTβ drop values equal to field defaults (e.g.,0,false, empty).NON_ABSENTβ dropOptional.empty()(keeps non-empty Optionals).
Per-field override: you can also put @JsonInclude(...) directly on a field.
3) @JsonIgnore / @JsonIgnoreProperties β hide or tolerate extras¶
Why: hide sensitive/internal fields, or ignore unknown JSON fields so clients donβt break you.
@JsonIgnoreProperties(ignoreUnknown = true) // ignore extra JSON input
public class AccountDto {
private String email;
@JsonIgnore // never show in JSON
private String passwordHash;
}
Input with extras β Object
Object β Output
Useful options (max 5)
ignoreUnknown = trueβ skip unexpected input fields.value = {"field1","field2"}β ignore these by name.allowGetters = trueβ still serialize (getters allowed).allowSetters = trueβ still deserialize (setters allowed).@JsonIgnore(field or getter) β hard hide that one property.
Tip: prefer
@JsonIgnorefor single fields; use@JsonIgnorePropertiesfor class-level rules.
4) @JsonFormat β make dates predictable¶
Why: you control the exact text format instead of gambling on defaults.
public class EventDto {
@JsonFormat(shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd'T'HH:mm:ssXXX")
private ZonedDateTime startsAt;
}
Object β Output
Useful options (max 5)
shape = STRINGβ format as text (common for dates).shape = NUMBERβ epoch millis/seconds (with@JsonFormat+ config).pattern = "..."β custom date pattern.timezone = "UTC"β force a TZ (orEurope/Vilnius).locale = "en"β locale for month/day names, etc.
5) @JsonAlias β accept many input names, output one¶
Why: smooth migrations: multiple incoming names map to one field.
public class ProductDto {
@JsonProperty("price") // output as "price"
@JsonAlias({"cost", "amount"}) // accept these on input
private BigDecimal price;
}
Valid Inputs
Object β Output
6) @JsonCreator β build immutable objects from JSON¶
Why: records/immutables or no no-arg constructor? Tell Jackson which ctor/factory to use.
public class PointDto {
private final int x;
private final int y;
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public PointDto(@JsonProperty("x") int x,
@JsonProperty("y") int y) {
this.x = x; this.y = y;
}
}
Input β Object
Useful modes (max 5)
Mode.PROPERTIESβ match by property names (most common).Mode.DELEGATINGβ pass the whole input into one param (single-value wrapper).- Works with static factory +
@JsonCreatortoo. - Combine with
@JsonProperty(required = true)on params. - Pair with
@JsonValueon the other side to serialize as a single value.
7) @JsonValue β serialize as a single value (enums/value objects)¶
Why: make an enum or tiny object show up as a simple string/number.
public enum Role {
ADMIN("admin"), USER("user");
private final String label;
Role(String l) { this.label = l; }
@JsonValue
public String toJson() { return label; }
}
Enum β Output
For deserializing back, add a @JsonCreator factory that turns the string into the enum/value object.
8) Bidirectional relations β stop infinite loops¶
Problem: parent β child β parent β β¦ during serialization.
Option A: βmanaged/backβ pair
public class OrderDto {
@JsonManagedReference
private List<OrderItemDto> items;
}
public class OrderItemDto {
@JsonBackReference
private OrderDto order;
}
Object β Output (simplified)
Option B: identity by id
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class CategoryDto {
private Long id;
private String name;
private List<CategoryDto> children;
private CategoryDto parent;
}
Object β Output
{
"id": 1,
"name": "Root",
"children": [ { "id": 2, "name": "Child", "parent": 1 } ],
"parent": null
}
When to choose
- Use A for simple parentβchild where you just want to drop the back link.
- Use B when the graph is more complex or you want references by id.
9) Polymorphism β @JsonTypeInfo + @JsonSubTypes¶
Why: a field/collection holds different concrete types; you need a type hint.
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = CardPaymentDto.class, name = "card"),
@JsonSubTypes.Type(value = BankTransferDto.class, name = "bank")
})
public abstract class PaymentDto { }
public class CardPaymentDto extends PaymentDto { public String cardLast4; }
public class BankTransferDto extends PaymentDto { public String iban; }
Object β Output
Input β Object
Useful options for @JsonTypeInfo (max 5)
use = Id.NAMEβ symbolic names (most common with@JsonSubTypes).use = Id.CLASSβ fully qualified class name (tightly couples API to Java).include = As.PROPERTYβ add"type": "..."field (most common).include = As.EXISTING_PROPERTYβ reuse an existing field as the type key.property = "type"β name of that discriminator field.
quick βwhen do i use what?β map¶
- Names donβt match? β
@JsonProperty(maybe withaccess). - Hide or tolerate extras? β
@JsonIgnore,@JsonIgnoreProperties. - Too many nulls/empties? β
@JsonIncludewithNON_NULL/NON_EMPTY. - Dates weird? β
@JsonFormat(pattern + timezone). - Legacy input names? β
@JsonAlias. - Immutable DTOs? β
@JsonCreator(+@JsonPropertyparams). - Graph cycles? β managed/back or identity ids.
- Mixed subtypes? β
@JsonTypeInfo+@JsonSubTypes.
tiny end-to-end example (mixing a few)¶
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class UserDto {
@JsonProperty("id") private Long id;
@JsonProperty("name") private String fullName;
@JsonIgnore private String internalNote;
@JsonProperty(value = "email", access = JsonProperty.Access.WRITE_ONLY)
private String email; // input only
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate birthday;
@JsonProperty("role")
private Role role; // enum with @JsonValue as shown earlier
}
Input
{
"id": 10,
"name": "Sam",
"email": "sam@ex.com",
"birthday": "1990-05-01",
"role": "admin",
"extra": "ignored client junk"
}
Output
jackson advanced annotations & patterns¶
Jackson has many more annotations and patterns for advanced use cases. Here are some of the most useful ones, explained with examples.
1) global vs per-class: when to annotate vs configure¶
Rule of thumb
- If itβs about a DTOβs contract, prefer annotations (portable, self-documenting).
- If itβs a cross-cutting policy, prefer mapper config (Spring Boot auto-config or your
ObjectMapperbean).
(No snippet here because you asked to include code only where annotations appear. Below, everything is annotation-first.)
2) @JsonInclude β advanced tricks (field-level + value filters)¶
Why: fine-tune which values are omitted.
// class-level: drop nulls by default
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ProductDto {
private String name;
// field override: drop empty strings/collections too
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private String description;
// drop defaults (e.g., 0, false, empty) β great for PATCH-like responses
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
private int stock;
// drop Optional.empty but keep present values
@JsonInclude(JsonInclude.Include.NON_ABSENT)
private Optional<String> ean;
// contentInclude: drop nulls inside collections/maps (keeps container)
@JsonInclude(content = JsonInclude.Include.NON_NULL)
private List<String> tags;
}
Object β Output
{
"name": "Desk",
"stock": 0, // NOTE: kept unless default value is actually 0 at field init
"tags": [] // container kept; null elements dropped
}
Useful modes (max 5)
ALWAYS,NON_NULL,NON_EMPTY,NON_DEFAULT,NON_ABSENTcontent = ...β apply rule to elements of collections/maps
tip:
NON_DEFAULTcompares to the fieldβs initialized default. Ifint stock = 0;, zero will be dropped; if uninitialized, zero may be kept.
3) @JsonProperty β access, required, defaults, ordering partner¶
Why: total control over naming and read/write visibility.
@JsonPropertyOrder({"id","name","email"})
public class UserDto {
@JsonProperty("id")
private Long id;
// write-only: accepted on input, hidden on output
@JsonProperty(value = "email", access = JsonProperty.Access.WRITE_ONLY, required = true, defaultValue = "unknown@local")
private String email;
// read-only: shown in output, ignored on input
@JsonProperty(value = "name", access = JsonProperty.Access.READ_ONLY)
private String fullName;
}
Input β Object
Object β Output
Useful options (max 5)
value = "..."(json name)access = READ_ONLY | WRITE_ONLY | READ_WRITErequired = truedefaultValue = "..."(used if missing on input)- pair with
@JsonPropertyOrder(stable field order)
4) @JsonSetter / @JsonDeserialize β input rules & null handling¶
Why: control how JSON becomes fields.
public class SettingsDto {
// treat empty string as null on input
@JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP)
private String theme;
// custom deserializer for tricky field (e.g., "yes"/"no" β boolean)
@JsonDeserialize(using = YesNoBooleanDeserializer.class)
private boolean marketingConsent;
}
Input β Object
β theme stays null (skipped), marketingConsent becomes true.
Useful options (max 5)
@JsonSetter(nulls = Nulls.SKIP | Nulls.SET | Nulls.FAIL)contentNulls = ...(for collection/map elements)@JsonDeserialize(using = YourDeserializer.class)@JsonDeserialize(contentUsing = ... , keyUsing = ...)(elements/keys)@JsonDeserialize(builder = ...)(for builder patterns)
5) @JsonSerialize β output shaping (single field or keys/elements)¶
Why: teach Jackson how to write a field.
public class ReportDto {
// custom serializer: mask last 6 of IBAN
@JsonSerialize(using = MaskingSerializer.class)
private String iban;
// map with non-string keys β serialize keys in a custom way
@JsonSerialize(keyUsing = ComplexKeySerializer.class)
private Map<UUID, Integer> balancesByAccount;
}
Object β Output (conceptual)
{ "iban": "LT****************90", "balancesByAccount": { "acc:550e8400-e29b-41d4-a716-446655440000": 120 } }
Useful options (max 5)
using = ...(value serializer)keyUsing = ...(map key serializer)contentUsing = ...(collection/map element serializer)- works with
@JsonFormat(dates/numbers) β format first, then serialize - combine with
@JsonIncludeto omit post-serialization empties
6) @JsonAnySetter / @JsonAnyGetter β βthe rest of the fieldsβ¶
Why: capture unknown/variable properties into a map and optionally emit them back.
public class FlexibleDto {
private Map<String, Object> other = new HashMap<>();
@JsonAnySetter
public void put(String name, Object value) { other.put(name, value); }
@JsonAnyGetter
public Map<String, Object> getOther() { return other; }
}
Input β Object
Object β Output
Useful notes (max 5)
- great with evolving schemas
- coexists with
@JsonIgnoreProperties(ignoreUnknown = true)(but you usually pick one) - values can be typed (
Map<String, JsonNode>or specific DTO) - pair with validation after deserialization
- beware of over-accepting junk (log or validate)
7) @JsonUnwrapped β flatten nested objects¶
Why: emit a child objectβs fields at the parent level (and read them back).
public class OrderDto {
private String id;
@JsonUnwrapped(prefix = "ship_", suffix = "")
private AddressDto shipping;
}
Object β Output
Useful options (max 5)
prefix = "...",suffix = "..."(avoid collisions)- works both ways (serialize + deserialize)
- avoid name clashes with parent fields
- can nest multiple unwrapped sub-objects with careful prefixes
- combine with
@JsonIncludeon the nested type
8) @JsonNaming β automatic casing conventions¶
Why: avoid sprinkling @JsonProperty everywhere for simple case mappings.
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class MetricsDto {
private long requestCount;
private double errorRate;
}
Object β Output
Useful strategies (max 5)
SnakeCaseStrategySNAKE_CASE(enum alias)LowerCamelCaseStrategy(default)KebabCaseStrategyUpperCamelCaseStrategy
tip: prefer
@JsonPropertyfor exceptions,@JsonNamingfor bulk rules.
9) @JsonView β role-based field visibility¶
Why: one DTO, different audiences (public vs admin).
public class Views { public static class Public{} public static class Admin extends Public{} }
public class UserDto {
@JsonView(Views.Public.class)
public Long id;
@JsonView(Views.Public.class)
public String displayName;
@JsonView(Views.Admin.class)
public String email;
}
Serialize with a view (Spring controller example)
Public Output
Admin Output
Useful notes (max 5)
- views compose via inheritance
- annotate fields and/or getters
- controller-level
@JsonViewapplies to response - tests: use
mapper.writerWithView(Views.Public.class) - donβt overuse; it increases complexity
10) polymorphism deeper β names, existing/external type ids, wrappers¶
Why: control where the type info lives.
// common base
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = CardPaymentDto.class, name = "card"),
@JsonSubTypes.Type(value = BankTransferDto.class, name = "bank")
})
public abstract class PaymentDto {}
// optional: name at subtype (instead of listing in @JsonSubTypes)
@JsonTypeName("card")
public class CardPaymentDto extends PaymentDto {
public String cardLast4;
}
Variants (most used)
include = As.EXISTING_PROPERTYβ reuse a real field as the discriminator.include = As.EXTERNAL_PROPERTYβ type sits next to the property holding the object (used inside containers).include = As.WRAPPER_OBJECTβ{"card": {...}}style.
I/O (wrapper example)
Useful options (max 5)
use = Id.NAME | Id.CLASS | Id.MINIMAL_CLASSinclude = As.PROPERTY | As.EXISTING_PROPERTY | As.EXTERNAL_PROPERTY | As.WRAPPER_OBJECTproperty = "type"visible = true(keep the type property in POJO if also a real field)defaultImpl = ...(fallback subtype)
11) identity & cycles advanced β scopes and resolvers¶
Why: control how object identity is generated and resolved.
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
resolver = MyIdResolver.class, // optional custom lookups
scope = ProjectDto.class // id uniqueness per scope
)
public class ProjectDto {
public Long id;
public String name;
public List<ProjectDto> children;
}
Output (with refs)
Useful knobs (max 5)
generator = PropertyGenerator | IntSequenceGenerator | UUIDGeneratorproperty = "..."for property-based idsscope = ...limit id spaceresolver = ...custom id resolution- pair with
@JsonIdentityReference(alwaysAsId = true)to always emit just the id
12) mixins β annotate without touching the class¶
Why: 3rd-party classes or you want βannotation profilesβ.
// mixin type: only annotations, same shape as target
public abstract class UserMixin {
@JsonProperty("id") abstract Long getId();
@JsonIgnore abstract String getInternalNote();
}
// register mixin with mapper (Spring bean config, not shown per your ask)
Effect: your DTO JSON changes as if you had annotated the original class.
Useful notes (max 5)
- great for libraries/records you canβt modify
- test different API contracts in tests
- combine with views or filters
- keep mixins close to config for discoverability
- annotate getters/setters/fields in the mixin
13) dynamic filters β @JsonFilter¶
Why: choose at runtime which fields to include.
@JsonFilter("userFilter")
public class UserDto {
public Long id;
public String name;
public String email;
}
(You apply a FilterProvider at write time β code lives outside the DTO; included here just to show the annotation anchor.)
Useful notes (max 5)
- per-write control (good for ad-hoc projections)
- combine with
SimpleBeanPropertyFilter - can whitelist or blacklist fields
- filters stack with other annotations
- more flexible but harder to reason about than
@JsonView
14) records & immutables β minimal ceremony¶
Why: concise DTOs with stable contracts.
public record CustomerDto(
@JsonProperty("id") Long id,
@JsonProperty("name") String name,
@JsonFormat(pattern = "yyyy-MM-dd") LocalDate since
) {}
Input
Output
quick chooser (deep cut)¶
- Mass renaming β
@JsonNaming; exceptions β@JsonProperty. - Selective omission β
@JsonInclude(field or content-level). - Input cleanup β
@JsonSetter(nulls=...)or custom@JsonDeserialize. - Dynamic & role-based views β
@JsonViewor@JsonFilter(runtime). - Graphy models β
@JsonManagedReference/@JsonBackReference(simple) or@JsonIdentityInfo(complex). - Flat API shape β
@JsonUnwrapped(use prefixes). - Polymorphism β
@JsonTypeInfo+@JsonSubTypes(pickincludemode carefully).