Repository navigation
NonNullableValueCoercedAsNullException being thrown in QueryTraversal #2761
Description
Discussed in #2760
Originally posted by slisaasquatch March 12, 2022
I'm seeing random NonNullableValueCoercedAsNullExceptions from our servers running graphql-java 17.3 with graphql-java-tools 12.0.2. I've logged the entire GraphQL query, variables, and operation name, and I cannot reproduce the exception. The stack trace looks like this:
graphql.execution.NonNullableValueCoercedAsNullException: Field '' has coerced Null value for NonNull type 'String!'
at graphql.execution.ValuesResolver.externalValueToInternalValue(ValuesResolver.java:493)
at graphql.execution.ValuesResolver.lambda$externalValueToInternalValueForList$2(ValuesResolver.java:604)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1384)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:566)
at graphql.execution.ValuesResolver.externalValueToInternalValueForList(ValuesResolver.java:605)
at graphql.execution.ValuesResolver.externalValueToInternalValue(ValuesResolver.java:507)
at graphql.execution.ValuesResolver.externalValueToInternalValueForObject(ValuesResolver.java:570)
at graphql.execution.ValuesResolver.externalValueToInternalValue(ValuesResolver.java:510)
at graphql.execution.ValuesResolver.externalValueToInternalValue(ValuesResolver.java:491)
at graphql.execution.ValuesResolver.externalValueToInternalValueForVariables(ValuesResolver.java:412)
at graphql.execution.ValuesResolver.coerceVariableValues(ValuesResolver.java:94)
at graphql.analysis.QueryTraverser.coerceVariables(QueryTraverser.java:64)
at graphql.analysis.QueryTraverser.<init>(QueryTraverser.java:60)
at graphql.analysis.QueryTraverser.<init>(QueryTraverser.java:40)
at graphql.analysis.QueryTraverser$Builder.build(QueryTraverser.java:297)
at graphql.analysis.MaxQueryDepthInstrumentation.newQueryTraverser(MaxQueryDepthInstrumentation.java:92)
at graphql.analysis.MaxQueryDepthInstrumentation.lambda$beginValidation$2(MaxQueryDepthInstrumentation.java:57)
at graphql.execution.instrumentation.SimpleInstrumentationContext.onCompleted(SimpleInstrumentationContext.java:51)
at graphql.execution.instrumentation.ChainedInstrumentation$ChainedInstrumentationContext.lambda$onCompleted$1(ChainedInstrumentation.java:238)
at graphql.com.google.common.collect.ImmutableList.forEach(ImmutableList.java:405)
at graphql.execution.instrumentation.ChainedInstrumentation$ChainedInstrumentationContext.onCompleted(ChainedInstrumentation.java:238)
at graphql.GraphQL.validate(GraphQL.java:600)
at graphql.GraphQL.parseAndValidate(GraphQL.java:562)
at graphql.GraphQL.lambda$parseValidateAndExecute$10(GraphQL.java:531)
at graphql.execution.preparsed.NoOpPreparsedDocumentProvider.getDocument(NoOpPreparsedDocumentProvider.java:15)
at graphql.GraphQL.parseValidateAndExecute(GraphQL.java:533)
at graphql.GraphQL.executeAsync(GraphQL.java:502)
Can someone please give me some general directions on what the error means?
I know if I put a null inside an array that requires non null values in variables, I can cause an error with the same error message. However, if you look at the stack trace, the exception was thrown at graphql.GraphQL.executeAsync, meaning I'm not even getting a result from it.
Also for the record, this is what my variables looks like:
{
"accountId": "133...63",
"userId": "133...63",
"userInput": {
"accountId": "133...63",
"id": "133...63"
}
}I do not have arrays or nulls in my variables.
Activity
bbakerman commentedon Mar 13, 2022
Ok I have had a look at this
What is happening is that during the
QueryTraversalcreationValuesResolvertries to turn the input variables into the internal representation for the runtime.During one of these calls, something is returning null when the input type is says its non null
Now it's possible that the starting value here was non null but the call to a scalar say ended up returning a null value perhaps.
I see a couple of problems in the code. First off tjhe lack of "field name or type" in the error message ios a mistake. We should fix that.
The other problem (debatable) is the re-use of
NonNullableValueCoercedAsNullExceptionI think this is a mistake in that yes its a nullable value on a non null type but should we have a specific exception for output variables versus input variables to help with semantic understanding. Anyway that can be discussed and debated.I think we should
As to the root cause, I suspect a scalar is breaking contract here.
The
parseValuecall is never meant to turn null - as the input is never null. It's either CoercingParseValueException or non null value.@slisaasquatch do you have any custom scalars in place? Could one of them be breaking contract?
slisaasquatch commentedon Mar 13, 2022
Thanks a lot for looking into this. Yes, I have quite a few custom scalars, and I'll take a look at them.
slisaasquatch commentedon Mar 13, 2022
I checked every single one of my custom scalar implementations, and I don't think it's possible for any of the
parseValuemethods to return null. Also, the original query in question didn't actually use any custom scalars. Is it possible that something else can trigger this error as well?bbakerman commentedon Mar 14, 2022
Yeah of course - it could be a bug. We need to do a reproduction. Can you send us in the schema and query for the exception above please.
We have the variables (or we can make them up) but we don't have the original query nor the backing schema that might lead to this. If you can send this in direct or use our anonymizer if you want to protect you type names
https://github.com/graphql-java/graphql-anonymizer
slisaasquatch commentedon Mar 14, 2022
Sounds good. I've attached the schema and the query in a gist here since they are quite large. The operation name used is
UpsertUserMutation.slisaasquatch commentedon Mar 14, 2022
Hi, after doing more digging, it looks like it's my own fault, because the
variableswas actually{ "accountId": "133...63", "userId": "133...63", "userInput": { "accountId": "133...63", "id": "133...63", "referredByCodes": [null] } }And the
referredByCodesfield got lost in our logging framework, and it's now obvious that that's what's causing the problem.johnloven commentedon Mar 15, 2022
I get this
when sending
{ "variables":{}, "query":"mutation ($input: UpdateMatrixInput!) { updateMatrix(input: $input) { id } }" }with a schema containing
which makes sense.
The problem is that this exception doesn't trigger anything to be returned to the client, so the client waits for a response until it times out.
Having the input inline instead
{ "query":"mutation { updateMatrix { id } }" }results in an error being returned to the client immediately
{ "errors":[ { "message":"Validation error of type MissingFieldArgument: Missing field argument input @ 'updateMatrix'", "locations":[ { "line":1, "column":12 } ], "extensions":{ "classification":"ValidationError" } } ], "data":null }which is what I would want in the first case as well.
Is there a way to return the error to the client in the first case as well?
bbakerman commentedon Mar 16, 2022
You are using kickstart as the framework around graphql-java. I am not sure how it handles exceptions so cant comment on the "timeout" issue.
But I do want to have a look at the coercing problem via
MaxQueryDepthInstrumentation- I suspect we have case where it should not be running as isbbakerman commentedon Mar 16, 2022
I have replicated this problem in a test here: #2763
I am yet unsure what we will do here... This is still in the investigation phase
dondonz commentedon Mar 18, 2022
I'll fix up this exception so it's more useful
dondonz commentedon Jun 4, 2022
Fixed by #2773