Skip to content

Infer DELEGATING mode for single-Record-argument Creator (3.x) #6015

@seonwooj0810

Description

@seonwooj0810

Background

Follow-up to #5222 — that thread has long history and conflicting ideas, so per @cowtowncoder's request this issue captures only the cleaned-up plan.

In #5222, @cowtowncoder wrote:

I think it'd be reasonable to change "mode" auto-detection/heuristics to consider single-Record-arg Constructor be considered delegating

This issue proposes exactly that, scoped tightly to keep regression risk low, and gated behind a new MapperFeature for opt-out.

Motivation

General single-argument Creator auto-detection is fundamentally ambiguous (properties vs delegating), which is why Jackson requires explicit @JsonCreator(mode = ...) today. Java record types resolve that ambiguity structurally:

  • A record has well-defined components with names and types
  • A constructor Foo(SomeRecord r) cannot reasonably mean "treat SomeRecord's components as named properties of Foo" — they belong to a different type
  • The only consistent interpretation is DELEGATING: deserialize the JSON as SomeRecord, then pass it to Foo's constructor

This makes record-argument Creators a safe, narrow case where auto-detection adds real value over requiring @JsonCreator(mode = DELEGATING).

Proposed change

Extend POJOPropertiesCollector._isDelegatingConstructor(PotentialCreator) (and the equivalent path for static factory methods) to also return true when ALL of the following hold:

  1. The Creator has exactly one parameter
  2. The parameter type isRecord()
  3. The declaring class has no other Creator carrying an explicit @JsonCreator annotation (so an explicit annotation always wins)
  4. The new MapperFeature is enabled

When the heuristic fires, the Creator is treated as if it carried @JsonCreator(mode = DELEGATING).

New MapperFeature

MapperFeature.INFER_CREATOR_FROM_RECORD_ARGUMENT — default enabled.

Javadoc will state:

  • What it does (one line)
  • Why it's record-specific (component metadata removes the usual 1-arg properties/delegating ambiguity)
  • That disabling restores pre-existing behavior exactly

Out of scope

Test plan

New behavior:

  • record-arg constructor alone → DELEGATING
  • record-arg static factory static Foo of(Rec r) → DELEGATING
  • record-arg constructor + no-arg constructor → record-arg wins for record-shaped JSON

Regression guards:

  • Explicit @JsonCreator(mode = PROPERTIES) on record-arg → PROPERTIES wins
  • Any other @JsonCreator present in the class → record-arg ignored
  • Non-record single-arg Creator → unchanged (no auto-detect)
  • Generic record parameter (Box<T>) → unchanged from current
  • MapperFeature disabled → byte-for-byte old behavior

Existing tests under records/, DelegatingCreatorsTest, RecordExplicitCreatorsTest, DelegatingCreatorImplicitNamesTest must continue to pass unchanged.

Target

Jackson 3.x (branch 3.x) — behavioral change.

Happy to take this on if the plan looks good.

Background discussion: #5222

Metadata

Metadata

Assignees

No one assigned

    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