There are two scenarios where Jackson’s built-in annotations are not enough: when you need to control exactly how a complex type is serialised, and when the class you need to annotate belongs to a third-party library whose source you cannot modify. For the first case, Jackson provides custom serialisers and deserialisers. For the second, it provides Mix-in Annotations — a mechanism that lets you attach annotations to any class without touching its source.
Runnable code: every example below is in the jackson3-by-example repository, compiled and executed against Jackson 3.2.1 on JDK 21 — E01 the serialiser, E02 the deserialiser, E03 module registration, E04 mix-ins and E05 ValueSerializer, with captured output in docs/part4-custom.md.
This is the area with the most API churn between Jackson 2 and Jackson 3, so the renames are worth having in one place before the code: JsonSerializer → ValueSerializer, JsonDeserializer → ValueDeserializer, SerializerProvider → SerializationContext, writeNumberField/writeStringField → writeNumberProperty/writeStringProperty, parser.getCodec().readTree(parser) → ctxt.readTree(parser), mapper.registerModule(m) → builder.addModule(m), mapper.addMixIn(a, b) → builder.addMixIn(a, b), and every throws IOException deleted. StdSerializer and StdDeserializer keep their names but move to tools.jackson.databind.
Writing a Custom Serialiser
Extend StdSerializer<T> and override serialize(). The method receives the value to write and a JsonGenerator you use to emit JSON tokens.
Suppose you have a Money type that should serialise as a structured JSON object containing the amount and the currency code:
public class Money {
private final BigDecimal amount;
private final String currencyCode;
// Constructor and getters
}