Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Building schema parser fails when java.util.List is used with a type parameter from a generic class #460

Open
devopsix opened this issue Nov 28, 2020 · 3 comments
Labels

Comments

@devopsix
Copy link

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.

image

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 parameter T instead of MutationInput.Item.
  • The issue does not occur if MutationInput has an own property java.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 for items (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.

  1. Extract attached ZIP file.
  2. 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".

@devopsix devopsix added the bug label Nov 28, 2020
@marcust
Copy link
Contributor

marcust commented Jan 6, 2021

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)

@laszlopalfi
Copy link

Hello guys,
I'm experiencing the same issue, any idea if this is going to be fixed?

@cornwe19
Copy link

cornwe19 commented Aug 31, 2021

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants