Skip to content

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

Closed

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

  1. 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)
    
  2. laszlopalfi commented on Jul 13, 2021

    @laszlopalfi

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

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

      Issue actions