Summary
AssociationDeserializer in jmolecules-jackson3 fails to deserialize Association fields when Jackson 3 selects a public parameterized constructor as an implicit property-based creator.
This occurs when a constructor parameter name matches a JSON property name but their types differ (AggregateRoot vs Association<AggregateRoot, Id>).
Environment
- jmolecules-integrations: 0.33.0 (jmolecules-jackson3)
- Jackson: 3.0.0 (via Spring Boot 4.0.3)
- jmolecules-bytebuddy: 0.27.0
Reproduction
public class Order implements AggregateRoot<Order, Order.OrderId> {
private final Association<Customer, CustomerId> customer;
// no-arg constructor (e.g. added by jmolecules-bytebuddy for JPA)
public Order() { this.customer = null; }
// business constructor — parameter name "customer" matches JSON property
public Order(Customer customer) {
this.customer = Association.forAggregate(customer);
}
public Association<Customer, CustomerId> getCustomer() {
return this.customer;
}
}
Serialization produces:
{"customer": "550e8400-e29b-41d4-a716-446655440000"}
Deserialization fails with:
MismatchedInputException: Cannot construct instance of `Customer`
(although at least one Creator exists): no String-argument constructor/factory
method to deserialize from String value ('550e8400-...')
Root Cause
Jackson 3 changed its implicit creator auto-detection. When a public parameterized constructor has parameter names matching JSON property names, Jackson 3 selects it as an implicit property-based creator — even when a no-arg constructor is available. Jackson 2 preferred the no-arg constructor in this scenario.
- Jackson 3 sees
public Order(Customer customer) and detects that parameter customer matches JSON property "customer" → selects as implicit creator
- Jackson resolves the deserializer for the constructor parameter type (
Customer) instead of the field type (Association<Customer, CustomerId>)
AssociationDeserializer is never applied
- The serialized UUID string cannot be deserialized as
Customer → MismatchedInputException
Verification
| Constructor visibility |
Result |
| Both package-private |
✅ Pass (Jackson uses no-arg + field-based) |
| No-arg public, parameterized package-private |
✅ Pass |
| No-arg package-private, parameterized public |
❌ Fail |
| Both public |
❌ Fail |
The issue is triggered whenever the parameterized constructor is public. This commonly happens with jmolecules-bytebuddy, which adds a public no-arg constructor alongside the original public business constructor.
Workaround
Adding @JsonIgnore to the business constructor prevents Jackson 3 from selecting it as an implicit creator:
@JsonIgnore
public Order(Customer customer) {
this.customer = Association.forAggregate(customer);
}
Summary
AssociationDeserializerinjmolecules-jackson3fails to deserializeAssociationfields when Jackson 3 selects a public parameterized constructor as an implicit property-based creator.This occurs when a constructor parameter name matches a JSON property name but their types differ (
AggregateRootvsAssociation<AggregateRoot, Id>).Environment
Reproduction
Serialization produces:
{"customer": "550e8400-e29b-41d4-a716-446655440000"}Deserialization fails with:
Root Cause
Jackson 3 changed its implicit creator auto-detection. When a public parameterized constructor has parameter names matching JSON property names, Jackson 3 selects it as an implicit property-based creator — even when a no-arg constructor is available. Jackson 2 preferred the no-arg constructor in this scenario.
public Order(Customer customer)and detects that parametercustomermatches JSON property"customer"→ selects as implicit creatorCustomer) instead of the field type (Association<Customer, CustomerId>)AssociationDeserializeris never appliedCustomer→MismatchedInputExceptionVerification
The issue is triggered whenever the parameterized constructor is
public. This commonly happens withjmolecules-bytebuddy, which adds apublicno-arg constructor alongside the originalpublicbusiness constructor.Workaround
Adding
@JsonIgnoreto the business constructor prevents Jackson 3 from selecting it as an implicit creator: