Skip to content

bug: DefaultInputObjectMapper.getFieldType(declaredField, TargetClass) does not work for collections with bounded generic implementations #1385

@lbusch25

Description

@lbusch25

Expected behavior

Given an implementing class with a bounded generic type:

@Builder
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class TaskProperties<F extends TaskField> implements Serializable {

    @Serial
    private static final long serialVersionUID = 1115187246981180834L;

    private List<F> fields;
}

And the below schema implementation:

type Query {
    getFakeGeneric: TaskProperties
}

type Mutation {
    testGenericFieldTypeNotFound(taskProperties: TaskPropertyInput!): TaskProperties
}

type TaskProperties {
    fields: [TaskField]
}

type TaskField {
    fieldName: String
    value: Object #corresponds to a FieldType implementation - can be wrapped Java type or custom
    type: FieldType
}

input TaskPropertyInput {
    fields: [TaskConfigurationInput]
}

input TaskConfigurationInput {
    fieldName: String
    value: Object
    type: FieldType
}

The TaskPropertyInput should correctly map to the TaskProperties java class during DGS's deserialization of the input object. Instead an error is thrown. Note that this same object can be resolved correctly as a return type, and that it works via REST mapping (with the default ObjectMapper implementation).

Actual behavior

The below exception is thrown around identifying class F:

org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.util.LinkedHashMap<?, ?>] to type [@javax.validation.constraints.NotNull @com.netflix.graphql.dgs.InputArgument com.lawsonbusch.inputdemo.domain.TaskProperties<com.lawsonbusch.inputdemo.domain.TaskConfigurationField<?>>] for value '{fields=[{fieldName=test field, value=string value, type=STRING}]}'; nested exception is java.lang.ClassNotFoundException: F
	at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:47) ~[spring-core-5.3.23.jar:5.3.23]
	at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:192) ~[spring-core-5.3.23.jar:5.3.23]
	at com.netflix.graphql.dgs.internal.method.AbstractInputArgumentResolver.convertValue(AbstractInputArgumentResolver.kt:93) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.internal.method.AbstractInputArgumentResolver.resolveArgument(AbstractInputArgumentResolver.kt:49) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.internal.method.ArgumentResolverComposite.resolveArgument(ArgumentResolverComposite.kt:39) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.internal.DataFetcherInvoker.get(DataFetcherInvoker.kt:62) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at graphql.schema.DataFetcherFactories.lambda$wrapDataFetcher$2(DataFetcherFactories.java:37) ~[graphql-java-19.3.jar:na]
	at graphql.execution.instrumentation.dataloader.DataLoaderDispatcherInstrumentation.lambda$instrumentDataFetcher$0(DataLoaderDispatcherInstrumentation.java:86) ~[graphql-java-19.3.jar:na]
	at graphql.execution.ExecutionStrategy.fetchField(ExecutionStrategy.java:282) ~[graphql-java-19.3.jar:na]
	at graphql.execution.ExecutionStrategy.resolveFieldWithInfo(ExecutionStrategy.java:211) ~[graphql-java-19.3.jar:na]
	at graphql.execution.ExecutionStrategy.resolveField(ExecutionStrategy.java:183) ~[graphql-java-19.3.jar:na]
	at graphql.execution.AsyncSerialExecutionStrategy.lambda$execute$1(AsyncSerialExecutionStrategy.java:47) ~[graphql-java-19.3.jar:na]
	at graphql.execution.Async.eachSequentiallyImpl(Async.java:191) ~[graphql-java-19.3.jar:na]
	at graphql.execution.Async.eachSequentially(Async.java:180) ~[graphql-java-19.3.jar:na]
	at graphql.execution.AsyncSerialExecutionStrategy.execute(AsyncSerialExecutionStrategy.java:42) ~[graphql-java-19.3.jar:na]
	at graphql.execution.Execution.executeOperation(Execution.java:159) ~[graphql-java-19.3.jar:na]
	at graphql.execution.Execution.execute(Execution.java:105) ~[graphql-java-19.3.jar:na]
	at graphql.GraphQL.execute(GraphQL.java:645) ~[graphql-java-19.3.jar:na]
	at graphql.GraphQL.lambda$parseValidateAndExecute$11(GraphQL.java:564) ~[graphql-java-19.3.jar:na]
	at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1187) ~[na:na]
	at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2309) ~[na:na]
	at graphql.GraphQL.parseValidateAndExecute(GraphQL.java:559) ~[graphql-java-19.3.jar:na]
	at graphql.GraphQL.executeAsync(GraphQL.java:527) ~[graphql-java-19.3.jar:na]
	at com.netflix.graphql.dgs.internal.BaseDgsQueryExecutor.baseExecute(BaseDgsQueryExecutor.kt:127) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.internal.DefaultDgsQueryExecutor.execute(DefaultDgsQueryExecutor.kt:79) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.mvc.DgsRestController$graphql$executionResult$1.invoke(DgsRestController.kt:206) ~[graphql-dgs-spring-webmvc-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.mvc.DgsRestController$graphql$executionResult$1.invoke(DgsRestController.kt:204) ~[graphql-dgs-spring-webmvc-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.internal.utils.TimeTracer.logTime(TimeTracer.kt:24) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.mvc.DgsRestController.graphql(DgsRestController.kt:204) ~[graphql-dgs-spring-webmvc-5.5.0.jar:5.5.0]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:681) ~[tomcat-embed-core-9.0.65.jar:4.0.FR]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.23.jar:5.3.23]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[tomcat-embed-core-9.0.65.jar:4.0.FR]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
	at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
Caused by: java.lang.ClassNotFoundException: F
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) ~[na:na]
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) ~[na:na]
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) ~[na:na]
	at java.base/java.lang.Class.forName0(Native Method) ~[na:na]
	at java.base/java.lang.Class.forName(Class.java:375) ~[na:na]
	at com.netflix.graphql.dgs.internal.DefaultInputObjectMapper.mapToJavaObject(DefaultInputObjectMapper.kt:125) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at com.netflix.graphql.dgs.internal.method.InputObjectMapperConverter.convert(InputObjectMapperConverter.kt:40) ~[graphql-dgs-5.5.0.jar:5.5.0]
	at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41) ~[spring-core-5.3.23.jar:5.3.23]
	... 78 common frames omitted

Steps to reproduce

I have created a public repo on my github profile that contains a test project, with instructions on how to run the project (basic maven build and run from IntelliJ). This project also has example queries in the ReadMe.md and java docs that described the expected errors and known workarounds.

Note that if the generic type F is replaced with a concrete implementation, an exception is still thrown.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions