GeezerVison is showing github.com — reject null variable value for @skip and @include if argument by nab-iha3 · Pull Request #4540 · graphql-java/graphql-java
Conversation
spock, SkipAndInclude, query($flag: Boolean = true) { field @skip(if: $flag) } executed with {"flag": null}:
java.lang.NullPointerException: Cannot invoke "java.lang.Boolean.booleanValue()" because the return value of "java.util.Map.get(Object)" is null
at graphql.execution.conditional.ConditionalNodes.getIfValue
the @skip/@include if argument is Boolean!, but a nullable variable with a non-null default value is a legal usage in that non-null position (the spec's IsVariableUsageAllowed), so the query passes validation. when the client then sends an explicit null, the coerced value is null and getIfValue does (boolean) variables.get(name), so the unboxing throws an uncaught NPE straight out of execution. the identical query written with $flag: Boolean! already returns a proper coercion error, and so does a null variable in a non-null list item or input field; the directive if argument is the one non-null position that was missed. the same NPE fires for @include, for nested fields, and from ExecutableNormalizedOperationFactory.
getIfValue now raises an AbortExecutionException (surfaced as a normal query error with data: null) when the resolved variable value is null for the non-null if argument, so the response carries an error instead of a thrown exception. the without-variables normalization path (variables == null) still returns null to defer the decision, so valid inputs, including the applied-default case, are unchanged.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Test Report
Test Results
| Java Version |
Total |
Passed |
Failed |
Errors |
Skipped |
| Java 17 |
6278 (+16 🟢) |
6222 (+16 🟢) |
0 (±0) |
0 (±0) |
56 (±0) |
| Java 21 |
6278 (+16 🟢) |
6222 (+16 🟢) |
0 (±0) |
0 (±0) |
56 (±0) |
| Java 25 |
6278 (+16 🟢) |
6222 (+16 🟢) |
0 (±0) |
0 (±0) |
56 (±0) |
| jcstress |
32 (±0) |
32 (±0) |
0 (±0) |
0 (±0) |
0 (±0) |
| Total |
18866 (+48 🟢) |
18698 (+48 🟢) |
0 (±0) |
0 (±0) |
168 (±0) |
Code Coverage (Java 25)
| Metric |
Covered |
Missed |
Coverage |
vs Master |
| Lines |
30384 |
3078 |
90.8% |
±0.0% |
| Branches |
8975 |
1505 |
85.6% |
±0.0% |
| Methods |
8104 |
1186 |
87.2% |
±0.0% |
Changed Class Coverage (4 classes)
| Class |
Line |
Branch |
Method |
| g.e.c.ConditionalNodes |
+0.9% 🟢 |
+1.3% 🟢 |
±0.0% |
| g.e.Execution |
+0.3% 🟢 |
±0.0% |
±0.0% |
| g.e.ExecutionStrategy |
+0.1% 🟢 |
±0.0% |
±0.0% |
| g.e.NonNullableValueCoercedAsNullException |
+9.0% 🟢 |
+50.0% 🟢 |
+10.9% 🟢 |
Full HTML report: build artifact jacoco-html-report
Updated: 2026-11-06 14:18:20 UTC
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Reviewed 06a04107a6015dc96c4cf0c282031faed9631129, including its interaction with the already-merged #4522. The null check is needed, but I would request changes to the error handling.
1. [P1] Nested directive failures abort the whole operation.
The new AbortExecutionException discards successful sibling results. With a nullable parent field and variables {"flag": null}:
query($flag: Boolean = true) {
ok
parent { child @skip(if: $flag) }
}
I verified that both ok and parent execute, but the response contains only errors, classified as ExecutionAborted; data is absent. The nested failure should instead null the enclosing nullable parent, preserve the successful ok result, and report the error path. The same issue occurs with @include, asynchronous parent fetchers, mutations, and list selections. See the spec's execution-error handling.
Subscription handling also remains incomplete. For:
subscription($flag: Boolean = true) {
event { child @skip(if: $flag) }
}
with the same variables, a source publisher containing two events emits no GraphQL results: the subscriber receives onError(AbortExecutionException) and the stream terminates. I reproduced this for both directives.
2. [P2] Preserve the source location and execution path.
The message-only exception has neither locations nor path, although the offending variable-reference AST node is available. Preserve its source location and attach the execution path when handling a nested failure. The spec recommends locations for identifiable document positions and requires paths for errors associated with a result field.
Relationship to #4522: that PR forces field argument coercion before invoking a data fetcher, within the existing field-error handling. It preserves sibling data and null propagation. This PR addresses directive arguments during field collection, so the changes complement each other; #4522 does not cover this path. The remaining work is to establish consistent coercion-error handling at the field-collection boundaries, rather than simply substituting another exception class.
Coverage: the two added tests cover only root fields. Their result.data == null assertion also accepts both absent data and an explicit "data": null. Please add nested nullable/non-null parents, partial results, paths/locations, and subscription-event cases. I ran 62 existing targeted cases successfully and 49 additional probes covering synchronous/asynchronous execution, mutations, lists, defaults, normalization, and subscriptions. The probes confirm the behavior described above; no production changes were made during the review.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
agreed on both, reworked in bbeed5b.
getIfValue now throws NonNullableValueCoercedAsNullException carrying the location of the offending variable reference instead of AbortExecutionException. the two field-collection boundaries handle it:
- nested (
ExecutionStrategy.completeValue, around completeValueForObject): it's treated as a field error on the field whose sub-selection failed. the error gets that field's path plus the variable's location, and the field completes as null through the normal non-null validator, so a nullable parent is nulled, a non-null one propagates, and siblings are kept. that covers async parents, mutations and list items since they all go through completeValue, and subscription events too, so each event now emits {event: null} with the error and the stream keeps going.
- root (
Execution.executeOperation, around the root collectFields): there's no enclosing field to null, so it returns the error with no data entry, same as the $flag: Boolean! variable coercion failure.
ExecutableNormalizedOperationFactory and the QueryTraverser path now throw the same typed exception instead of the NPE.
tests in SkipAndInclude now cover, for both directives: root (asserts data absent from the spec map, location, no path), nested nullable parent with ok preserved for query and mutation, async parent, non-null parent propagating to the nullable grandparent with path [wrapper, inner], list items with paths [items, 0] / [items, 1], a two-event subscription with no onError, and the normalized operation. the 16 new cases fail on the previous production code; execution/normalized/analysis suites pass.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This branch has not been deployed
No deployments
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|