Skip to content

AssociationDeserializer not applied when Jackson 3 selects implicit property-based creator #398

@ogawa-takeshi

Description

@ogawa-takeshi

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.

  1. Jackson 3 sees public Order(Customer customer) and detects that parameter customer matches JSON property "customer" → selects as implicit creator
  2. Jackson resolves the deserializer for the constructor parameter type (Customer) instead of the field type (Association<Customer, CustomerId>)
  3. AssociationDeserializer is never applied
  4. The serialized UUID string cannot be deserialized as CustomerMismatchedInputException

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);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions