Skip to content

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

@devopsix

Description

@devopsix

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".

Activity

marcust

marcust commented on Jan 6, 2021

@marcust
Contributor

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

laszlopalfi commented on Jul 13, 2021

@laszlopalfi

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

cornwe19

cornwe19 commented on Aug 31, 2021

@cornwe19

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @marcust@cornwe19@laszlopalfi@devopsix

        Issue actions

          Building schema parser fails when java.util.List is used with a type parameter from a generic class · Issue #460 · graphql-java-kickstart/graphql-java-tools