Skip to content

Prefer getters over fluent setters for input field types - #833

Merged
oryan-block merged 2 commits into
masterfrom
bugfix/446
Oct 4, 2026
Merged

oryan-block merged 2 commits into
masterfrom
bugfix/446

Conversation

@oryan-block

Copy link
Copy Markdown
Collaborator

Fixes #446

Checklist

  • Pull requests follows the contribution guide
  • New or modified functionality is covered by tests

Description

A schema with input RepairApplyInput { repairMan: RepairManInput } and a mutation that takes RepairManInput directly fails to build with Two different classes used for type RepairManInput, where one of the two classes is RepairApply itself. The only workaround in the thread is duplicating the input type under another name. The reporter didn't post their entity classes, but you get the exact same error when the entity has a fluent setter, like the public RepairApply repairMan(RepairMan repairMan) { ...; return this; } JHipster generates, or the repairMan() + repairMan(RepairMan) pair from Lombok's @Accessors(fluent = true). Without the fluent setter the same schema builds fine on master.

SchemaClassScanner#findInputValueTypeInType finds the Java type of an input field by taking the public methods named x or getX, sorting them by name length and using the return type of the first non-synthetic one. It never looked at parameters. repairMan(RepairMan) has a shorter name than getRepairMan(), so it wins and RepairManInput gets mapped to its return type, RepairApply. That clashes with the RepairMan parameter of createRepairMan.

Now getters without parameters come first (non-synthetic first, same as before), then the public field, and only then methods with parameters, picked the same way as before. My first version dropped methods with parameters completely, but that broke schemas that build on master. For example a class used for both type Foo { bar: Bar } and input FooInput { bar: BarInput } with getBar(DataFetchingEnvironment), setBar(Bar) and a private field. If BarInput is only reachable through FooInput.bar, it never got discovered and makeExecutableSchema() threw Expected type 'BarInput' to be a GraphQLInputType, but it wasn't!. Same for an input class that only has a fluent setter. Keeping them as the last resort means those resolve exactly as on master. scanner finds input field types through getters with arguments covers that case. scanner ignores fluent setters when finding input field types covers the issue with a JHipster-style property, a Lombok-style pair and a @JvmField field, each with a fluent setter, and fails on master with the same error as the issue.

I didn't try to use a fluent setter's parameter type when there's no getter or public field. Telling a fluent setter apart from a getter with arguments would need a heuristic (one parameter, returns the declaring type) that gets things like Foo getParent(env) on Foo or Map.remove(Object) wrong. So a class with only a fluent setter x(X), or with x(X) and getX(env) but no getter without parameters, still resolves to the setter's return type, same as on master. Neither is needed for #446.

Behaviour change: when an input class has a getter without parameters (or a public field) and a method with parameters for the same field, the field's Java type now comes from the getter or field. So schemas that failed with "Two different classes used for type X" because of a fluent setter now build. Classes that only have methods with parameters for a field resolve as before.

🤖 Generated with Claude Code

oryan-block and others added 2 commits October 3, 2026 18:29
To find the Java type of an input object field, the scanner took any
public method named `x` or `getX`, picked the one with the shortest
name and used its return type. A fluent setter such as
`RepairApply repairMan(RepairMan)`, as generated by JHipster or Lombok
@accessors(fluent = true), has the shortest name, so the input type of
the field was mapped to the enclosing class. That fails with "Two
different classes used for type" as soon as the same input type is
also used as a resolver parameter.

Look for a getter without parameters first, then for a public field.
Methods with parameters, such as `getX(DataFetchingEnvironment)`, are
only used when neither exists, so nested input types that can only be
found through them are still discovered.

Fixes #446

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Oct 4, 2026

Copy link
Copy Markdown

@oryan-block
oryan-block merged commit e656974 into master Oct 4, 2026
6 checks passed
@oryan-block
oryan-block deleted the bugfix/446 branch October 4, 2026 17:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Two different classes used for type when model has relationship

1 participant