GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Add JSpecify annotations to language package nodes - redo (12 more) by dondonz · Pull Request #4257 · graphql-java/graphql-java

Conversation

@dondonz

@dondonz dondonz commented Feb 22, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Summary

Adds JSpecify nullability annotations (@NullMarked, @NullUnmarked, @Nullable) to the following graphql.language classes and interfaces, removing each from the JSpecifyAnnotationsCheck exemption list:

  • NodeTraverser
  • NonNullType
  • ObjectField
  • ObjectTypeDefinition / ObjectTypeExtensionDefinition
  • OperationDefinition
  • OperationTypeDefinition
  • PrettyAstPrinter
  • SDLDefinition / SDLExtensionDefinition
  • SDLNamedDefinition / TypeDefinition

Key decisions

Key interesting point - NamedNode.getName() is @Nullable
OperationDefinition can be anonymous (e.g. { user { name } }) — the GraphQL spec makes the operation name optional. Pushing @Nullable onto NamedNode.getName() is the truthful representation of the interface contract.

TypeDefinition.getName() overrides as non-null
TypeDefinition extends both SDLNamedDefinition (non-null getName()) and NamedNode (@Nullable getName()). SDL type definitions always have a name, so TypeDefinition explicitly overrides getName() to return non-null. This prevents @Nullable from cascading into callers like TypeDefinitionRegistry that work with concrete schema types.

OperationDefinition.selectionSet is non-null
Legacy convenience constructors that passed null for selectionSet were deleted (they were only used in one test). The field, constructor, and getSelectionSet() are all non-null, matching the GraphQL spec requirement that every operation has a selection set.

OperationDefinition.operation is @Nullable
The shorthand query form (e.g. { user { name } }) omits the query keyword, so getOperation() can legitimately return null.

Builder inner classes are @NullUnmarked
All builder static inner classes are annotated @NullUnmarked rather than annotating each field individually.

PrettyAstPrinter private helpers
node(@Nullable Class startClass), isEmpty(@Nullable ...), nvl(@Nullable ...), block(... @Nullable String separatorSingleLine, @Nullable String whenEmpty), and spaced(@Nullable String... args) / join(String, @Nullable String...) are nullable because null is actually passed at the call sites or because the method bodies already handle null gracefully.

Cascading fixes

  • OperationValidator: getName() is now @Nullable on OperationDefinition, so error message construction uses a pre-checked local variable or Objects.toString(x, "") fallback for the anonymous operation case.
  • TypeDefinitionRegistry: two Assert.assertNotNull(t.getName(), ...) workarounds are removed now that TypeDefinition.getName() is non-null.

🤖 Generated with Claude Code

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
dondonz added 6 commits February 22, 2026 10:27
@dondonz
Add corrections to prompt
b261a43
@dondonz
Add first lot of changes
4df4205
@dondonz
Add operation definition
8fb836e
@dondonz
Add pretty ast printer
298fea2
@dondonz
Override name as non null
82712c7
@dondonz
Add operation validator
7a9e810
@github-actions

github-actions Bot commented Feb 22, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Test Results

  335 files  ±0    335 suites  ±0   5m 8s ⏱️ +3s
5 376 tests ±0  5 367 ✅ ±0  9 💤 ±0  0 ❌ ±0 
5 465 runs  ±0  5 456 ✅ ±0  9 💤 ±0  0 ❌ ±0 

Results for commit 1063251. ± Comparison against base commit 50c22c1.

This pull request removes 196 and adds 172 tests. Note that renamed tests count towards both.
	?

	, expected: combo-\"\\\b\f\n\r\t, #4]
                __schema { types { fields { args { type { name fields { name }}}}}}
                __schema { types { fields { type { name fields { name }}}}}
                __schema { types { inputFields { type { inputFields { name }}}}}
                __schema { types { interfaces { fields { type { interfaces { name } } } } } }
                __schema { types { name} }
                __type(name : "t") { name }
                a1: __schema { types { name} }
                a1: __type(name : "t") { name }
…
graphql.AssertTest ‑ assertFalse with different number of error args but false does not throw assertions [toRun: <graphql.AssertTest$__spock_feature_0_21prov0_closure23@296e281a delegate=inaccessible owner=inaccessible thisObject=inaccessible resolveStrategy=inaccessible directive=inaccessible parameterTypes=inaccessible maximumNumberOfParameters=inaccessible bcw=inaccessible thisType=inaccessible>, expectedMessage: error arg1, #0]
graphql.AssertTest ‑ assertFalse with different number of error args but false does not throw assertions [toRun: <graphql.AssertTest$__spock_feature_0_21prov0_closure24@59cda16e delegate=inaccessible owner=inaccessible thisObject=inaccessible resolveStrategy=inaccessible directive=inaccessible parameterTypes=inaccessible maximumNumberOfParameters=inaccessible bcw=inaccessible thisType=inaccessible>, expectedMessage: error arg1 arg2, #1]
graphql.AssertTest ‑ assertFalse with different number of error args but false does not throw assertions [toRun: <graphql.AssertTest$__spock_feature_0_21prov0_closure25@5dd903be delegate=inaccessible owner=inaccessible thisObject=inaccessible resolveStrategy=inaccessible directive=inaccessible parameterTypes=inaccessible maximumNumberOfParameters=inaccessible bcw=inaccessible thisType=inaccessible>, expectedMessage: error arg1 arg2 arg3, #2]
graphql.AssertTest ‑ assertFalse with different number of error args throws assertions [toRun: <graphql.AssertTest$__spock_feature_0_20prov0_closure20@70887727 delegate=inaccessible owner=inaccessible thisObject=inaccessible resolveStrategy=inaccessible directive=inaccessible parameterTypes=inaccessible maximumNumberOfParameters=inaccessible bcw=inaccessible thisType=inaccessible>, expectedMessage: error arg1, #0]
graphql.AssertTest ‑ assertFalse with different number of error args throws assertions [toRun: <graphql.AssertTest$__spock_feature_0_20prov0_closure21@56da7487 delegate=inaccessible owner=inaccessible thisObject=inaccessible resolveStrategy=inaccessible directive=inaccessible parameterTypes=inaccessible maximumNumberOfParameters=inaccessible bcw=inaccessible thisType=inaccessible>, expectedMessage: error arg1 arg2, #1]
graphql.AssertTest ‑ assertFalse with different number of error args throws assertions [toRun: <graphql.AssertTest$__spock_feature_0_20prov0_closure22@599e4d41 delegate=inaccessible owner=inaccessible thisObject=inaccessible resolveStrategy=inaccessible directive=inaccessible parameterTypes=inaccessible maximumNumberOfParameters=inaccessible bcw=inaccessible thisType=inaccessible>, expectedMessage: error arg1 arg2 arg3, #2]
graphql.AssertTest ‑ assertNotNull with different number of  error args throws assertions [toRun: <graphql.AssertTest$__spock_feature_0_5prov0_closure3@fab35b1 delegate=inaccessible owner=inaccessible thisObject=inaccessible resolveStrategy=inaccessible directive=inaccessible parameterTypes=inaccessible maximumNumberOfParameters=inaccessible bcw=inaccessible thisType=inaccessible>, expectedMessage: error arg1, #0]
graphql.AssertTest ‑ assertNotNull with different number of  error args throws assertions [toRun: <graphql.AssertTest$__spock_feature_0_5prov0_closure4@5c77ba8f delegate=inaccessible owner=inaccessible thisObject=inaccessible resolveStrategy=inaccessible directive=inaccessible parameterTypes=inaccessible maximumNumberOfParameters=inaccessible bcw=inaccessible thisType=inaccessible>, expectedMessage: error arg1 arg2, #1]
graphql.AssertTest ‑ assertNotNull with different number of  error args throws assertions [toRun: <graphql.AssertTest$__spock_feature_0_5prov0_closure5@4a734c04 delegate=inaccessible owner=inaccessible thisObject=inaccessible resolveStrategy=inaccessible directive=inaccessible parameterTypes=inaccessible maximumNumberOfParameters=inaccessible bcw=inaccessible thisType=inaccessible>, expectedMessage: error arg1 arg2 arg3, #2]
graphql.AssertTest ‑ assertNotNull with different number of error args with non null does not throw assertions [toRun: <graphql.AssertTest$__spock_feature_0_6prov0_closure6@10a0fe30 delegate=inaccessible owner=inaccessible thisObject=inaccessible resolveStrategy=inaccessible directive=inaccessible parameterTypes=inaccessible maximumNumberOfParameters=inaccessible bcw=inaccessible thisType=inaccessible>, expectedMessage: error arg1, #0]
…

♻️ This comment has been updated with latest results.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@dondonz

dondonz commented Feb 22, 2026

Copy link
Copy Markdown
Member Author

This is a redo of a previous PR that went off the rails

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

dondonz
dondonz commented Feb 22, 2026
View reviewed changes
Comment thread .claude/commands/jspecify-annotate.md
- **Do not remove interfaces** from public classes (e.g., if a class implements `NamedNode`, it must continue to do so).
- **Be extremely careful when changing methods to return `@Nullable`**. If an interface contract (or widespread ecosystem usage) expects a non-null return value, changing it to `@Nullable` is a breaking change that will cause compilation errors or `NullPointerException`s for callers. For example, if a method returned `null` implicitly but its interface requires non-null, you must honor the non-null contract (e.g., returning an empty string or default value instead of `null`).
- **Do not change the binary signature** of methods or constructors in a way that breaks backwards compatibility.

@dondonz dondonz Feb 22, 2026

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previous attempt randomly deleted a constructor parameter which caused that PR to go off the rails. Let's fix that here

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
dondonz
dondonz commented Feb 22, 2026
View reviewed changes
Comment thread src/main/java/graphql/language/NamedNode.java
* @return the name of this node, or null if this node is anonymous (e.g. an anonymous operation definition)
*/
String getName();
@Nullable String getName();

@dondonz dondonz Feb 22, 2026

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has to be nullable because an operation definition might not have a name

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
dondonz
dondonz commented Feb 22, 2026
View reviewed changes
Comment thread src/main/java/graphql/language/OperationDefinition.java
this.selectionSet = selectionSet;
}

public OperationDefinition(String name,

@dondonz dondonz Feb 22, 2026

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These constructors date back to 2018, no longer needed with the builders we have today.

I am removing these lines because these parameters should be not-nullable.

I would usually first deprecate the methods, then remove, but if I keep them here with a deprecated annotation, the nullability annotations will not be correct.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@dondonz dondonz Feb 22, 2026

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically a breaking change

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@dondonz dondonz added the breaking change requires a new major version to be relased label Feb 22, 2026
@dondonz dondonz changed the title Add JSpecify annotations to language package nodes (batch 3) Add JSpecify annotations to language package nodes - redo Feb 22, 2026
@dondonz
dondonz marked this pull request as draft February 22, 2026 05:09
dondonz added 3 commits February 22, 2026 16:38
@dondonz
Adjust AST Printer
dd49296
@dondonz
Account for a nullable op name
9e31132
@dondonz
Fix test for non-null selection set
1063251
dondonz
dondonz commented Feb 22, 2026
View reviewed changes
Comment thread src/main/java/graphql/analysis/QueryTraverser.java Outdated
OperationDefinition.Operation op = operationDefinition.getOperation() != null
? operationDefinition.getOperation()
: OperationDefinition.Operation.QUERY;
switch (op) {

@dondonz dondonz Feb 22, 2026

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some follow on changes after adding nullability annotations

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
dondonz
dondonz commented Feb 22, 2026
View reviewed changes
Comment thread src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy
def operationDefinition = OperationDefinition.newOperationDefinition()
.name("q")
.selectionSet(SelectionSet.newSelectionSet().selection(new Field("f")).build())
.build()

@dondonz dondonz Feb 22, 2026

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor test change after selection set not nullable

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@dondonz
dondonz marked this pull request as ready for review February 22, 2026 05:52
@dondonz dondonz changed the title Add JSpecify annotations to language package nodes - redo Add JSpecify annotations to language package nodes - redo (12 more) Feb 28, 2026
bbakerman
bbakerman reviewed Mar 7, 2026
View reviewed changes
Comment thread src/main/java/graphql/language/ObjectField.java
@Override
public ObjectField deepCopy() {
return new ObjectField(name, deepCopy(this.value), getSourceLocation(), getComments(), getIgnoredChars(), getAdditionalData());
return new ObjectField(name, assertNotNull(deepCopy(this.value), "value deepCopy should not return null"), getSourceLocation(), getComments(), getIgnoredChars(), getAdditionalData());

@bbakerman bbakerman Mar 7, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used to think assertion messages matter but they dont. A stacktrace would lead to this code location - so less code can be better code

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@dondonz dondonz Mar 7, 2026

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assert is a special one to satisfy the NullAway check

The problem is that deepCopy is a generic utility and I can't guarantee that always returns not-nullable result

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
bbakerman
bbakerman reviewed Mar 7, 2026
View reviewed changes
Comment thread src/main/java/graphql/language/OperationDefinition.java
public OperationDefinition(String name) {
this(name, null, emptyList(), emptyList(), null, null, emptyList(), IgnoredChars.EMPTY, emptyMap());
}

@bbakerman bbakerman Mar 7, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough - cruft must be removed at some point

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
bbakerman
bbakerman reviewed Mar 7, 2026
View reviewed changes
Comment thread src/main/java/graphql/language/OperationDefinition.java Outdated
}

public Operation getOperation() {
public @Nullable Operation getOperation() {

@bbakerman bbakerman Mar 7, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ummmmmm can this be null??? if its not specified surely is defaults to QUERY??

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@dondonz dondonz Mar 8, 2026

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep I agree, should have defaulted to query instead

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@dondonz dondonz Mar 8, 2026

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does in the parser as well

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
bbakerman
bbakerman approved these changes Mar 7, 2026
View reviewed changes
@bbakerman bbakerman added this to the 26.x is a breaking change release (other fixes as well) milestone Mar 7, 2026
@dondonz @claude
Make OperationDefinition.operation non-null, default to QUERY
f4e0f1b
Per the GraphQL spec and graphql-js reference implementation, the
operation type is always known — shorthand queries are QUERY. Default
the Builder to Operation.QUERY and remove null checks in callers.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@github-actions

github-actions Bot commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Test Report

Test Results

Java Version Total Passed Failed Errors Skipped
Java 11 5671 (±0) 5614 (±0) 0 (±0) 0 (±0) 57 (±0)
Java 17 5671 (±0) 5613 (±0) 0 (±0) 0 (±0) 58 (±0)
Java 21 5671 (±0) 5613 (±0) 0 (±0) 0 (±0) 58 (±0)
Java 25 5671 (±0) 5613 (±0) 0 (±0) 0 (±0) 58 (±0)
jcstress 32 (±0) 32 (±0) 0 (±0) 0 (±0) 0 (±0)
Total 22716 (±0) 22485 (±0) 0 (±0) 0 (±0) 231 (±0)

Code Coverage (Java 25)

Metric Covered Missed Coverage vs Master
Lines 28699 3123 90.2% ±0.0%
Branches 8333 1507 84.7% ±0.0%
Methods 7680 1223 86.3% ±0.0%

Changed Class Coverage (4 classes)

Class Line Branch Method
g.e.i.d.ExhaustedDataLoaderDispatchStrategy +1.2% 🟢 +7.7% 🟢 ±0.0%
g.l.AstSorter ±0.0% +2.9% 🟢 ±0.0%
g.l.OperationDefinition +3.0% 🟢 ±0.0% +3.3% 🟢
g.l.OperationDefinition
$Builder
+0.3% 🟢 ±0.0% ±0.0%

Full HTML report: build artifact jacoco-html-report

Updated: 2026-03-08 00:51:01 UTC

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@dondonz
dondonz merged commit 53c0c82 into master Mar 21, 2026
11 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@github-actions github-actions Bot mentioned this pull request Mar 22, 2026
CI Failure DoctorCI Failure - Run #23398087408 - JSpecifyAnnotationsCheck exemption list conflict #4351
Closed
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@bbakerman bbakerman bbakerman approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

breaking change requires a new major version to be relased

Projects

None yet

Milestone

26.x is a breaking change release (ot...

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@dondonz @bbakerman
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.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.