Skip to content

Resolve union and interface values by the raw class of a parameterized type - #892

Merged
oryan-block merged 2 commits into
masterfrom
bugfix/parameterized-dictionary-type
Oct 19, 2026
Merged

oryan-block merged 2 commits into
masterfrom
bugfix/parameterized-dictionary-type

Conversation

@oryan-block

@oryan-block oryan-block commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Checklist

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

Description

Slice 3 of the resolver type cleanup, no issue.

When a generic class backs a GraphQL type through a parameterized return type, a union or interface field returning it as a supertype can't resolve it. For example:

type Query {
    box: StringBox!
    result: Result!
}

union Result = StringBox | Other
fun box(): Box<String> = Box("x")
fun result(): Any = Box("x")

SchemaClassScanner keeps Box<String> as the dictionary key for StringBox, but DictionaryTypeResolver looks up the value's class, which is the raw Box, then its superclasses, interfaces and finally the simple name. So the parameterized key never matches and every query of result throws from execute():

Expected object type with name 'Box' to exist for union 'Result', but it doesn't!

For an interface (named: Named returning a Label<String> : Named) the lookup reaches the Kotlin interface Named first, so it's an AssertException instead:

You have asked for named object type 'Named' but it's not an object type but rather a 'graphql.schema.GraphQLInterfaceType'

It only worked when the GraphQL type name matched the class name.

Now DictionaryTypeResolver also maps the raw class of each parameterized entry to its type, but only when a single entry has that raw class. With Box<String> -> StringBox and Box<Int> -> IntBox it's ambiguous, so it fails the same way as before (this isn't #186, which is one class for two GraphQL types). The map is built lazily on the first miss, so the schema build does no extra work.

The raw class is tried last, only when the old name isn't an object type in the schema, and otherwise it falls back to the old getObjectType(name) ?: throw. So every value that resolves today still gets the same type, and the other failures throw the same exception. E.g. if Box<T> extends Parent and Parent is mapped to an object type, a Box still resolves to Parent's type. A subclass like class MyBox : Box<String> isn't covered, only the value's own class is looked up.

Release note: a union or interface field now resolves a value of a generic class to the type its parameterized return type backs (a Box returned as Any resolves to StringBox when Box<String> backs StringBox) instead of throwing.

Folded the cases into GenericObjectTypesTest. nested generic type arguments are resolved gets a union field returning a Page and an interface field returning a Hit, both failed on master. generic type arguments that are lists are not erased gets a union over its two EntityMatch<...> types, which still throws the TypeResolverError. The new a raw class keeps its own type when a parameterized entry shares it maps the raw EntityMatch to AnyEntityMatch next to EntityMatch<String> -> StringEntityMatch, and an EntityMatch still resolves to AnyEntityMatch (it fails if the raw class lookup goes first). mvn clean verify passes on JDK 25 against graphql-java 26.1 and 25.1 (348 tests), and on JDK 11 (347).

Also checked it through the installed jar in a separate consumer project. On master the union and interface fields throw the errors above, on this branch they return StringBox and StringLabel, also with graphql-java 25.1 at runtime. The ambiguous version throws the same on both. javap of the changed classes is the same as 14.3.0.

@sonarqubecloud

Copy link
Copy Markdown

@oryan-block
oryan-block merged commit 21f3796 into master Oct 19, 2026
7 checks passed
@oryan-block
oryan-block deleted the bugfix/parameterized-dictionary-type branch October 19, 2026 23:29
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.

1 participant