Learn Java Data Mapper Json Xml Validation Part 015 Polymorphism Security
title: Learn Java Data Mapper, JSON/XML Processing & Validation - Part 015 description: Polymorphic deserialization dengan Jackson: JsonTypeInfo, JsonSubTypes, discriminator, sealed hierarchies, subtype registration, security risks, PolymorphicTypeValidator, dan production-safe design. series: learn-java-data-mapper-json-xml-validation seriesTitle: Learn Java Data Mapper, JSON/XML Processing & Validation order: 15 partTitle: Polymorphic Deserialization: Type Info, Subtypes, Sealed Hierarchies, Security Risks tags:
- java
- jackson
- polymorphism
- deserialization
- json-type-info
- json-sub-types
- sealed-classes
- security
- data-mapper
- contract date: 2026-06-29
Part 015 — Polymorphic Deserialization: Type Info, Subtypes, Sealed Hierarchies, Security Risks
Target skill: mampu mendesain polymorphic JSON contract yang eksplisit, aman, evolvable, dan tidak membuka celah deserialization gadget atau coupling ke class name internal.
Polymorphism adalah kebutuhan natural di sistem enterprise.
Contoh:
{
"type": "CARD",
"cardToken": "tok_123",
"last4": "4242"
}
atau:
{
"eventType": "case.escalated",
"caseId": "CASE-001",
"newQueue": "HIGH_RISK"
}
Dalam Java, ini sering dimodelkan sebagai interface, abstract class, sealed interface, atau sealed class.
public sealed interface PaymentMethod
permits CardPaymentMethod, BankTransferPaymentMethod, EWalletPaymentMethod {
}
Masalahnya: JSON tidak punya konsep Java subtype. JSON hanya punya object, array, string, number, boolean, null.
Agar Jackson bisa membuat subtype yang benar, JSON harus membawa type information, atau kita harus memilih subtype secara manual.
Mental model:
Polymorphic deserialization means allowing input data to influence which Java type is instantiated. That is powerful and dangerous.
1. Kaufman Deconstruction
Subskill polymorphic deserialization:
| Subskill | Kemampuan |
|---|---|
| Identify polymorphic boundary | Tahu kapan payload benar-benar polymorphic |
| Choose discriminator | Menentukan field seperti type, kind, eventType, method |
| Use logical names | Menghindari class name sebagai wire contract |
| Register subtypes safely | Menggunakan @JsonSubTypes, named types, module, atau manual dispatcher |
| Model sealed hierarchy | Menggunakan Java sealed types untuk closed set |
| Handle unknown subtype | Reject, quarantine, preserve raw, atau route unknown |
| Avoid default typing risk | Tidak membiarkan JSON memilih arbitrary class |
| Test subtype matrix | Semua subtype valid/invalid/unknown diuji |
| Version contract | Menambah subtype tanpa merusak consumer |
| Separate event dispatch | Tidak semua polymorphism harus memakai Jackson polymorphic binding |
2. The Core Problem
Base type:
public interface NotificationChannel {
}
Subtypes:
public record EmailChannel(
String emailAddress
) implements NotificationChannel {}
public record SmsChannel(
String phoneNumber
) implements NotificationChannel {}
public record PushChannel(
String deviceToken
) implements NotificationChannel {}
Payload:
{
"emailAddress": "ana@example.com"
}
Jackson tidak tahu ini harus menjadi EmailChannel.
Butuh discriminator:
{
"type": "EMAIL",
"emailAddress": "ana@example.com"
}
Sekarang ada informasi untuk memilih subtype.
3. Safe Default: Logical Discriminator
Gunakan field contract-level seperti type, kind, eventType, method, bukan Java class name.
Good:
{
"type": "EMAIL",
"emailAddress": "ana@example.com"
}
Bad:
{
"@class": "com.example.notification.EmailChannel",
"emailAddress": "ana@example.com"
}
Kenapa class name buruk?
- mengekspos struktur internal
- refactor package/class menjadi breaking change
- membuka attack surface lebih besar
- membuat consumer tahu detail Java
- sulit dipakai non-Java consumer
- membingungkan saat versi berubah
Wire contract harus memakai logical names.
4. @JsonTypeInfo and @JsonSubTypes
Jackson annotation umum:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = EmailChannel.class, name = "EMAIL"),
@JsonSubTypes.Type(value = SmsChannel.class, name = "SMS"),
@JsonSubTypes.Type(value = PushChannel.class, name = "PUSH")
})
public sealed interface NotificationChannel
permits EmailChannel, SmsChannel, PushChannel {
}
Subtypes:
public record EmailChannel(
String emailAddress
) implements NotificationChannel {}
public record SmsChannel(
String phoneNumber
) implements NotificationChannel {}
public record PushChannel(
String deviceToken
) implements NotificationChannel {}
Input:
{
"type": "EMAIL",
"emailAddress": "ana@example.com"
}
Usage:
NotificationChannel channel =
objectMapper.readValue(json, NotificationChannel.class);
Result: EmailChannel.
5. include = As.PROPERTY
Most common shape:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
Output:
{
"type": "EMAIL",
"emailAddress": "ana@example.com"
}
Pros:
- readable
- easy for non-Java clients
- discriminator is inside object
- compatible with common API/event design
Cons:
- discriminator field must not conflict with business field
- field placement may matter for some streaming/custom parsing
- subtype classes may also need access to type field if domain wants it
6. include = As.EXISTING_PROPERTY
If subtype already has type field:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type",
visible = true
)
@JsonSubTypes({
@JsonSubTypes.Type(value = EmailChannel.class, name = "EMAIL"),
@JsonSubTypes.Type(value = SmsChannel.class, name = "SMS")
})
public sealed interface NotificationChannel permits EmailChannel, SmsChannel {
String type();
}
Subtype:
public record EmailChannel(
String type,
String emailAddress
) implements NotificationChannel {}
Input:
{
"type": "EMAIL",
"emailAddress": "ana@example.com"
}
Use when discriminator is truly part of the DTO/domain representation.
But avoid duplicating type field unless downstream really needs it.
7. Sealed Hierarchies
Java sealed types help express closed subtype sets.
public sealed interface PaymentMethod
permits CardPaymentMethod, BankTransferPaymentMethod, EWalletPaymentMethod {
}
public record CardPaymentMethod(
String cardToken
) implements PaymentMethod {}
public record BankTransferPaymentMethod(
String bankCode,
String accountNumber
) implements PaymentMethod {}
public record EWalletPaymentMethod(
String provider,
String walletId
) implements PaymentMethod {}
Sealed hierarchy benefits:
- subtype set explicit at compile time
- switch exhaustiveness possible
- API domain model clearer
- easier review of subtype additions
- matches many polymorphic contract use cases
But sealed type alone does not tell Jackson how to map JSON. You still need discriminator strategy or manual dispatch.
8. Polymorphic Request DTO Example
public record CreatePaymentRequest(
String requestId,
MoneyRequest amount,
PaymentMethod paymentMethod
) {}
Polymorphic field:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "methodType"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = CardPaymentMethod.class, name = "CARD"),
@JsonSubTypes.Type(value = BankTransferPaymentMethod.class, name = "BANK_TRANSFER"),
@JsonSubTypes.Type(value = EWalletPaymentMethod.class, name = "EWALLET")
})
public sealed interface PaymentMethod
permits CardPaymentMethod, BankTransferPaymentMethod, EWalletPaymentMethod {
}
Payload:
{
"requestId": "REQ-001",
"amount": {
"amount": "100.00",
"currency": "IDR"
},
"paymentMethod": {
"methodType": "CARD",
"cardToken": "tok_123"
}
}
This is explicit and contract-friendly.
9. Validation per Subtype
Each subtype can have its own validation.
public record CardPaymentMethod(
@NotBlank
String cardToken
) implements PaymentMethod {}
public record BankTransferPaymentMethod(
@NotBlank
@Pattern(regexp = "\\d{3}")
String bankCode,
@NotBlank
@Pattern(regexp = "\\d{10,16}")
String accountNumber
) implements PaymentMethod {}
Parent DTO must cascade:
public record CreatePaymentRequest(
@NotBlank String requestId,
@Valid @NotNull MoneyRequest amount,
@Valid @NotNull PaymentMethod paymentMethod
) {}
Flow:
Deserializer chooses type. Validation checks subtype-specific constraints.
10. Unknown Subtype Strategy
Input:
{
"methodType": "CRYPTO",
"wallet": "..."
}
What should happen?
Options:
| Strategy | Use Case |
|---|---|
| reject | commands/requests where unsupported action is invalid |
| route to unknown handler | event ingestion/webhook |
| preserve raw payload | audit/integration compatibility |
map to UnknownX subtype | tolerant readers |
| quarantine | asynchronous pipeline with unknown future events |
For command API, reject unknown type.
For event consumer, often preserve raw:
public record UnknownProviderEvent(
String eventType,
JsonNode rawPayload
) {}
Not all unknown polymorphism should be forced through Jackson annotations. Sometimes manual dispatcher is clearer.
11. Manual Dispatcher Pattern
For event envelopes, manual dispatch is often better.
Envelope:
public record EventEnvelope(
String eventId,
String eventType,
Instant occurredAt,
JsonNode payload
) {}
Dispatcher:
public void handle(JsonNode root) throws IOException {
EventEnvelope envelope = parseEnvelope(root);
switch (envelope.eventType()) {
case "case.created" -> {
CaseCreatedEvent payload =
objectMapper.treeToValue(envelope.payload(), CaseCreatedEvent.class);
caseCreatedHandler.handle(envelope, payload);
}
case "case.escalated" -> {
CaseEscalatedEvent payload =
objectMapper.treeToValue(envelope.payload(), CaseEscalatedEvent.class);
caseEscalatedHandler.handle(envelope, payload);
}
default -> unknownEventHandler.handle(envelope, root);
}
}
Advantages:
- unknown events can be stored/quarantined
- type routing is visible
- no arbitrary subtype construction
- event envelope remains stable
- different handlers can evolve independently
- better observability
Use Jackson polymorphic binding for structural subtypes inside one object. Use dispatcher for message routing.
12. Security Risk: Do Not Let JSON Pick Classes
Dangerous pattern:
{
"@class": "some.arbitrary.Class",
"..."
}
or enabling broad default typing without strict validator.
The risk: if attacker can influence class type and classpath contains gadget classes, deserialization can instantiate unexpected types or trigger dangerous behavior depending configuration/library versions.
Production rule:
Never allow untrusted JSON to specify arbitrary Java class names.
Avoid:
JsonTypeInfo.Id.CLASSfor untrusted inputJsonTypeInfo.Id.MINIMAL_CLASSfor untrusted input- broad default typing
- subtype auto-discovery from classpath for public input
- reflection-based subtype registration without allowlist
Prefer:
JsonTypeInfo.Id.NAME- explicit subtype allowlist
- sealed hierarchy
- manual dispatcher
PolymorphicTypeValidatorif default typing is unavoidable- strict mapper for command inputs
13. PolymorphicTypeValidator
When using default typing or more dynamic polymorphism, Jackson provides a validator mechanism to restrict allowed subtypes.
Conceptual example:
PolymorphicTypeValidator ptv =
BasicPolymorphicTypeValidator.builder()
.allowIfBaseType(AllowedBaseType.class)
.allowIfSubType("com.example.safe.")
.build();
ObjectMapper mapper = JsonMapper.builder()
.activateDefaultTyping(
ptv,
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY
)
.build();
But use this sparingly.
For most API/event contracts, explicit logical discriminators are better than default typing.
Validator is not a magic safety blanket. It must be reviewed like security policy.
14. Logical Names and Subtype Registry
Instead of annotation on base type, you can register named subtypes.
ObjectMapper mapper = JsonMapper.builder()
.build();
mapper.registerSubtypes(
new NamedType(EmailChannel.class, "EMAIL"),
new NamedType(SmsChannel.class, "SMS")
);
This is useful when:
- base type cannot be annotated
- subtype registration is module-owned
- different boundaries need different subtype sets
- generated/domain classes should stay annotation-free
But central registry must be governed:
- no duplicate logical names
- no unreviewed subtype additions
- tests for every registered subtype
- names are contract values
- deprecation process for names
15. Avoid Reflection-Based Auto-Registration for Untrusted Input
Some systems scan classpath for subtypes and register them automatically.
This looks convenient:
scan package com.example.events for all Event implementations
register all as subtypes
Risk:
- too broad
- hidden contract expansion
- subtype exposed accidentally
- security review bypassed
- build/runtime differences
- names derived from class names
- hard to deprecate
For public or cross-service contracts, prefer explicit registry.
16. Polymorphic Output Design
Serialization can include type info too.
PaymentMethod method = new CardPaymentMethod("tok_123");
String json = objectMapper.writeValueAsString(method);
Output:
{
"methodType": "CARD",
"cardToken": "tok_123"
}
Check output contract:
- Is discriminator included?
- Is discriminator name stable?
- Are subtype-specific fields present?
- Are fields ordered if humans/golden tests require it?
- Are secrets excluded?
- Are null fields included/omitted intentionally?
17. Polymorphism and MapStruct
Jackson chooses request subtype. MapStruct maps subtype to domain.
Request hierarchy:
public sealed interface PaymentMethodRequest
permits CardPaymentMethodRequest, BankTransferPaymentMethodRequest {
}
Domain hierarchy:
public sealed interface PaymentMethod
permits CardPayment, BankTransferPayment {
}
Mapper:
@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface PaymentMethodMapper {
default PaymentMethod toDomain(PaymentMethodRequest request) {
return switch (request) {
case CardPaymentMethodRequest card -> toDomain(card);
case BankTransferPaymentMethodRequest bank -> toDomain(bank);
};
}
CardPayment toDomain(CardPaymentMethodRequest request);
BankTransferPayment toDomain(BankTransferPaymentMethodRequest request);
}
This is explicit and compile-time friendly with sealed switch.
Avoid hidden reflection mapping for subtype transformations.
18. Polymorphic Collections
Payload:
{
"rules": [
{
"type": "MIN_AMOUNT",
"amount": "100.00"
},
{
"type": "COUNTRY_ALLOWED",
"countries": ["ID", "SG"]
}
]
}
Base:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = MinAmountRule.class, name = "MIN_AMOUNT"),
@JsonSubTypes.Type(value = CountryAllowedRule.class, name = "COUNTRY_ALLOWED")
})
public sealed interface EligibilityRule
permits MinAmountRule, CountryAllowedRule {
}
Container:
public record EligibilityPolicyRequest(
@NotEmpty
List<@Valid EligibilityRule> rules
) {}
Validation will cascade into each concrete subtype when @Valid is applied to list elements.
19. Deduction-Based Polymorphism
Some libraries/features allow choosing subtype based on field presence.
Example conceptual:
{ "emailAddress": "ana@example.com" }
means email, while:
{ "phoneNumber": "0812" }
means SMS.
This is fragile:
- ambiguous if fields overlap
- hard to evolve
- unclear errors
- new subtype can break old deduction
- poor contract readability
Prefer explicit discriminator for boundary contracts.
20. Polymorphism with XML
XML can use element name as discriminator:
<paymentMethod>
<card>
<cardToken>tok_123</cardToken>
</card>
</paymentMethod>
Or attribute:
<paymentMethod type="CARD">
<cardToken>tok_123</cardToken>
</paymentMethod>
For XML schema-first integration, discriminator style is often dictated by XSD.
Principle remains:
- logical discriminator
- explicit subtype set
- reject/handle unknown
- avoid Java class names
- test every subtype
21. Compatibility and Versioning
Adding a new subtype is not always backward-compatible.
Producer adds:
{
"methodType": "QRIS",
"qrCode": "..."
}
Old consumer behavior:
- fails if strict
- ignores if event unknown handler exists
- maps to unknown if tolerant design exists
For commands, adding new input subtype may be fine if server supports it, but clients need documentation.
For events, adding new event type can break consumers unless event contract says unknown event types may appear and should be ignored/quarantined.
Contract rule:
Consumers must tolerate unknown event types by storing/quarantining them.
Consumers of command request responses should not receive unknown subtypes unless API version changes.
22. Error Design
Unknown subtype error should be clean.
Bad:
Could not resolve type id 'QRIS' as a subtype of ...
Better API error:
{
"code": "UNSUPPORTED_PAYMENT_METHOD",
"field": "paymentMethod.methodType",
"message": "Unsupported payment method type: QRIS"
}
Invalid subtype field:
{
"code": "INVALID_FIELD",
"field": "paymentMethod.cardToken",
"message": "cardToken is required for CARD payment method"
}
Differentiate:
| Error | Example |
|---|---|
| missing discriminator | methodType is required |
| unsupported discriminator | methodType QRIS is unsupported |
| invalid subtype payload | cardToken is required |
| forbidden subtype | payment method not allowed for merchant |
| domain rejection | payment method disabled |
Deserializer handles first three. Business rules handle latter two.
23. Testing Strategy
23.1 Each Subtype Deserializes
@Test
void cardPaymentMethod_deserializes() throws Exception {
PaymentMethod method = mapper.readValue("""
{
"methodType": "CARD",
"cardToken": "tok_123"
}
""", PaymentMethod.class);
assertThat(method).isInstanceOf(CardPaymentMethod.class);
}
23.2 Unknown Type Rejected
@Test
void unknownPaymentMethod_rejected() {
assertThatThrownBy(() -> mapper.readValue("""
{
"methodType": "QRIS",
"qrCode": "..."
}
""", PaymentMethod.class))
.isInstanceOf(JsonProcessingException.class);
}
23.3 Missing Type Rejected
@Test
void missingPaymentMethodType_rejected() {
assertThatThrownBy(() -> mapper.readValue("""
{
"cardToken": "tok_123"
}
""", PaymentMethod.class))
.isInstanceOf(JsonProcessingException.class);
}
23.4 Validation Cascades to Subtype
@Test
void cardPaymentMethod_validatesCardToken() throws Exception {
CreatePaymentRequest request = mapper.readValue("""
{
"requestId": "REQ-001",
"amount": { "amount": "100.00", "currency": "IDR" },
"paymentMethod": {
"methodType": "CARD",
"cardToken": ""
}
}
""", CreatePaymentRequest.class);
Set<ConstraintViolation<CreatePaymentRequest>> violations =
validator.validate(request);
assertThat(violations)
.extracting(v -> v.getPropertyPath().toString())
.contains("paymentMethod.cardToken");
}
23.5 Serialization Golden Test
@Test
void cardPaymentMethod_serializesWithLogicalType() throws Exception {
PaymentMethod method = new CardPaymentMethod("tok_123");
String json = mapper.writeValueAsString(method);
assertThat(json).contains("\"methodType\":\"CARD\"");
assertThat(json).doesNotContain("com.example");
}
24. Production Checklist
Before approving polymorphic deserialization:
- Is polymorphism actually needed?
- Is discriminator explicit and logical?
- Are Java class names absent from JSON?
- Is subtype set explicit?
- Are sealed types used when set is closed?
- Are unknown subtypes handled intentionally?
- Is default typing avoided for untrusted input?
- If default typing is used, is
PolymorphicTypeValidatorstrict? - Are subtype-specific validation rules tested?
- Are subtype additions reviewed as contract changes?
- Does error model distinguish missing type, unknown type, and invalid subtype body?
- Is event routing better handled by manual dispatcher?
- Are secrets excluded in every subtype?
- Are all subtypes covered by golden serialization/deserialization tests?
25. Anti-Patterns
25.1 Class Name in JSON
{ "@class": "com.company.internal.payment.CardPaymentMethod" }
Avoid for external/untrusted input.
25.2 Broad Default Typing
Letting Jackson infer/accept many types without strict allowlist is dangerous.
25.3 Reflection Auto-Discovery as Contract
Contract should not expand because a class was added to classpath.
25.4 Business Routing in Deserializer
Deserializer should not decide workflow beyond representation type.
25.5 No Unknown Strategy
Unknown subtypes will happen in evolving systems. Decide now.
25.6 Subtype Without Validation
Each subtype has its own invariants. Validate them.
26. Mini Case Study: Regulatory Action Request
Request:
{
"caseId": "CASE-001",
"action": {
"actionType": "ESCALATE",
"targetQueue": "HIGH_RISK",
"reasonCode": "RISK_SIGNAL"
}
}
Hierarchy:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "actionType"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = EscalateActionRequest.class, name = "ESCALATE"),
@JsonSubTypes.Type(value = AssignActionRequest.class, name = "ASSIGN"),
@JsonSubTypes.Type(value = CloseActionRequest.class, name = "CLOSE")
})
public sealed interface CaseActionRequest
permits EscalateActionRequest, AssignActionRequest, CloseActionRequest {
}
Subtypes:
public record EscalateActionRequest(
@NotBlank String targetQueue,
@NotBlank String reasonCode
) implements CaseActionRequest {}
public record AssignActionRequest(
@NotBlank String assigneeUserId
) implements CaseActionRequest {}
public record CloseActionRequest(
@NotBlank String closureCode,
String note
) implements CaseActionRequest {}
Container:
public record PerformCaseActionRequest(
@NotBlank String caseId,
@Valid @NotNull CaseActionRequest action
) {}
Domain mapper:
@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface CaseActionMapper {
default CaseCommand toCommand(PerformCaseActionRequest request) {
return switch (request.action()) {
case EscalateActionRequest escalate -> new EscalateCaseCommand(
new CaseId(request.caseId()),
escalate.targetQueue(),
escalate.reasonCode()
);
case AssignActionRequest assign -> new AssignCaseCommand(
new CaseId(request.caseId()),
assign.assigneeUserId()
);
case CloseActionRequest close -> new CloseCaseCommand(
new CaseId(request.caseId()),
close.closureCode(),
close.note()
);
};
}
}
This keeps responsibility clean:
- Jackson chooses representation subtype.
- Validation checks subtype fields.
- Mapper creates domain command.
- Use case enforces workflow/state/authorization.
27. Practice Drill
Build polymorphic request for notification channels:
Types:
EMAIL:emailAddress,templateIdSMS:phoneNumber,messagePUSH:deviceToken,title,body
Tasks:
- Model sealed interface.
- Add
@JsonTypeInfowith logical discriminatorchannelType. - Add
@JsonSubTypes. - Add Jakarta Validation constraints per subtype.
- Write JSON examples.
- Write tests for each subtype.
- Write unknown subtype test.
- Write missing discriminator test.
- Write serialization test proving no Java class name is emitted.
- Map subtype to domain command using switch.
28. Summary
Polymorphic deserialization is one of the most powerful and dangerous parts of Jackson.
Mental model:
A discriminator is a contract value. Treat subtype registration as an allowlist.
Rules:
- Use logical discriminator names, not Java class names.
- Prefer
JsonTypeInfo.Id.NAMEover class/minimal class ids. - Use explicit subtype registration.
- Use sealed hierarchies for closed sets.
- Avoid broad default typing for untrusted input.
- Use
PolymorphicTypeValidatoronly as a strict allowlist mechanism when dynamic typing is unavoidable. - For event routing, manual dispatcher is often clearer and safer.
- Validate subtype-specific fields.
- Test every subtype and unknown/missing discriminator.
- Treat adding subtype as contract evolution.
Part berikutnya membahas Jackson modules: Java Time, Records, Optional/JDK8 types, parameter names, performance modules, custom module design, and module governance.
References
- Jackson Polymorphic Deserialization Docs: https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization
- Jackson
JsonTypeInfoJavadoc: https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/latest/com/fasterxml/jackson/annotation/JsonTypeInfo.html - Jackson
JsonSubTypesJavadoc: https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/latest/com/fasterxml/jackson/annotation/JsonSubTypes.html - Jackson Databind Security Notes: https://github.com/FasterXML/jackson-databind#security
- Jackson 3.0 Release Notes: https://github.com/FasterXML/jackson/wiki/Jackson-Release-3.0
You just completed lesson 15 in build core. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.
Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.