graphql-java-tools
graphql-java-tools copied to clipboard
Building schema parser fails when java.util.List is used with a type parameter from a generic class
Affected version: 6.2.0
Description
I am having difficulties backing an input object with a Java class that inherits a java.util.List<T>
property from its generic super class.
Given this schema:
type Query {
}
type Mutation {
mutate(input: MutationInput!): String!
}
input MutationInput {
items: [Item!]!
}
input Item {
prop: String!
}
And given this generic base class:
package com.example.generics;
import java.util.List;
public abstract class GenericMutationInput<T> {
public List<T> items;
}
And given this backing class for the MutationInput
input object:
package com.example.generics;
public class MutationInput extends GenericMutationInput<MutationInput.Item> {
public static class Item {
public String prop;
}
}
Then building the SchemaParser
object fails with this exception:
Caused by: java.lang.IllegalStateException: TypeUtils.getRawType(type, declaringType) must not be null
at graphql.kickstart.tools.GenericType$RelativeTo.replaceTypeVariable(GenericType.kt:156) ~[graphql-java-tools-6.2.0.jar:na]
It seems the parameters passed to TypeUtils.getRawType(Type, Type)
do not carry sufficient information for determining the raw type. The required information would to be contained in this.mostSpecificType
.
A few things I have already tried:
- It does not matter if
Item
is a nested class or not. The issue occurs in either case. - The issue does not occur if
String
is used for type parameterT
instead ofMutationInput.Item
. - The issue does not occur if
MutationInput
has an own propertyjava.util.List<MutationInput.Item>
and has no generic super class:package com.example.nogenerics; import java.util.List; public class MutationInput { public static class Item { public String prop; } public List<Item> items; }
- The issues does not occur if
MutationInput
overrides getter and setter foritems
(getters and setters not shown above for brevity).
Steps to reproduce the bug
I have created a minimal Spring Boot application for reproducing this issue.
Given you have OpenJDK 15 installed.
- Extract attached ZIP file.
- Run
./mvnw clean spring-boot:run "-Dspring-boot.run.profiles=generics"
.
Expected behavior: Starting the Spring Boot application succeeds.
Actual behavior: Starting the Spring Boot application fails with this root cause:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [graphql.kickstart.tools.SchemaParser]: Factory method 'schemaParser' threw exception; nested exception is java.lang.IllegalStateException: TypeU
tils.getRawType(type, declaringType) must not be null
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.1.jar:5.3.1]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:651) ~[spring-beans-5.3.1.jar:5.3.1]
... 138 common frames omitted
Caused by: java.lang.IllegalStateException: TypeUtils.getRawType(type, declaringType) must not be null
at graphql.kickstart.tools.GenericType$RelativeTo.replaceTypeVariable(GenericType.kt:156) ~[graphql-java-tools-6.2.0.jar:na]
at graphql.kickstart.tools.GenericType$RelativeTo.replaceTypeVariable(GenericType.kt:143) ~[graphql-java-tools-6.2.0.jar:na]
at graphql.kickstart.tools.GenericType$RelativeTo.unwrapGenericType(GenericType.kt:86) ~[graphql-java-tools-6.2.0.jar:na]
at graphql.kickstart.tools.TypeClassMatcher.match(TypeClassMatcher.kt:28) ~[graphql-java-tools-6.2.0.jar:na]
Starting the application with an alternative profile that replaces the backing class with a “non-generic” one succeeds: ./mvnw clean spring-boot:run "-Dspring-boot.run.profiles=nogenerics"
.
The same issue happens when the type that it tries to figure out is actually generic over multiple levels:
type Query {
item: Item!
}
type Item {
id: ID!
}
And backing Java classes like:
class AbstractItem<T> {
T id;
}
class BaseItem<T> extends AbstractItem<T> {}
class ConcreteItem extends BaseItem<Long> {}
leads to the same exception
Caused by: java.lang.IllegalStateException: TypeUtils.getRawType(type, declaringType) must not be null
at graphql.kickstart.tools.GenericType$RelativeTo.replaceTypeVariable(GenericType.kt:156)
at graphql.kickstart.tools.GenericType$RelativeTo.unwrapGenericType(GenericType.kt:86)
at graphql.kickstart.tools.TypeClassMatcher.match(TypeClassMatcher.kt:28)
at graphql.kickstart.tools.TypeClassMatcher.match(TypeClassMatcher.kt:23)
at graphql.kickstart.tools.SchemaClassScanner.scanResolverInfoForPotentialMatches(SchemaClassScanner.kt:274)
at graphql.kickstart.tools.SchemaClassScanner.scanQueueItemForPotentialMatches(SchemaClassScanner.kt:264)
at graphql.kickstart.tools.SchemaClassScanner.scanQueue(SchemaClassScanner.kt:113)
at graphql.kickstart.tools.SchemaClassScanner.scanForClasses(SchemaClassScanner.kt:74)
at graphql.kickstart.tools.SchemaParserBuilder.scan(SchemaParserBuilder.kt:154)
at graphql.kickstart.tools.SchemaParserBuilder.build(SchemaParserBuilder.kt:195)
at graphql.kickstart.tools.boot.GraphQLJavaToolsAutoConfiguration.schemaParser(GraphQLJavaToolsAutoConfiguration.java:152)
Hello guys, I'm experiencing the same issue, any idea if this is going to be fixed?
It seems like this issue also happens when a generic has a non-generic, non-concrete (interface or abstract class) type as well. For example,
interface A { val name: String }
class B: A {
override val name: String = "B"
}
class C: A {
override val name: String = "C"
}
class Resolver: GraphQLQueryResolver {
fun listAInstances(): Page<A> = ...
}
Schema
type Query {
listAInstances: APage
}
type APage {
contents: [A]
}
type A {
name: String
}
Is there any way to work around this without completely duplicating the graphql APage
and A
types in this situation? Or any update on when this might be resolved?