diff --git a/.claude/commands/jspecify-annotate.md b/.claude/commands/jspecify-annotate.md
index 7083a6b3da..b6e59da6af 100644
--- a/.claude/commands/jspecify-annotate.md
+++ b/.claude/commands/jspecify-annotate.md
@@ -4,6 +4,14 @@ Note that JSpecify is already used in this repository so it's already imported.
If you see a builder static class, you can label it `@NullUnmarked` and not need to do anymore for this static class in terms of annotations.
+## Batch Size and Prioritization
+
+Annotate approximately 10 classes per batch for optimal context management. Start with interface/simple classes first, then tackle complex classes with builders. This helps identify patterns early.
+
+## Exploration Phase
+
+Before annotating, use `grep` to search for how each class is instantiated (e.g., `grep -r "new Comment"`) to understand which parameters can be null. Check constructor calls, method returns, and field assignments to inform your nullability decisions.
+
Analyze this Java class and add JSpecify annotations based on:
1. Set the class to be `@NullMarked`
2. Remove all the redundant `@NonNull` annotations that IntelliJ added
@@ -12,6 +20,62 @@ Analyze this Java class and add JSpecify annotations based on:
5. Method implementations that return null or check for null
6. GraphQL specification details (see details below)
+## Pattern Examples
+
+Here are concrete examples of common annotation patterns:
+
+**Interface:**
+```java
+@PublicApi
+@NullMarked
+public interface MyInterface {
+ // Methods inherit @NullMarked context
+}
+```
+
+**Class with nullable field:**
+```java
+@PublicApi
+@NullMarked
+public class Comment {
+ private final String content;
+ private final @Nullable SourceLocation sourceLocation;
+
+ public Comment(String content, @Nullable SourceLocation sourceLocation) {
+ this.content = content;
+ this.sourceLocation = sourceLocation;
+ }
+
+ public @Nullable SourceLocation getSourceLocation() {
+ return sourceLocation;
+ }
+}
+```
+
+**Class with nullable return type:**
+```java
+@PublicApi
+@NullMarked
+public class Container {
+ public @Nullable Node getChildOrNull(String key) {
+ // May return null
+ return children.get(key);
+ }
+}
+```
+
+**Builder with @NullUnmarked:**
+```java
+@PublicApi
+@NullMarked
+public class MyClass {
+ @NullUnmarked
+ public static class Builder {
+ // No further annotations needed in builder
+ }
+}
+```
+
## GraphQL Specification Compliance
This is a GraphQL implementation. When determining nullability, consult the GraphQL specification (https://spec.graphql.org/draft/) for the relevant concept. Key principles:
@@ -19,7 +83,10 @@ The spec defines which elements are required (non-null) vs optional (nullable).
If a class implements or represents a GraphQL specification concept, prioritize the spec's nullability requirements over what IntelliJ inferred.
-## How to validate
+## Validation Strategy
+
+Run `./gradlew compileJava` after every 3-5 classes annotated, not just at the end. This catches issues early and makes debugging easier.
+
Finally, please check all this works by running the NullAway compile check.
If you find NullAway errors, try and make the smallest possible change to fix them. If you must, you can use assertNotNull. Make sure to include a message as well.
@@ -56,4 +123,3 @@ The bound on a type parameter determines whether nullable type arguments are all
- When the type parameter represents a concrete non-null object
- Even if some methods return `@Nullable T` (meaning "can be null even if T is non-null")
- Example: `Edge` with `@Nullable T getNode()` — node may be null, but T represents the object type
-
diff --git a/src/main/java/graphql/execution/ResultNodesInfo.java b/src/main/java/graphql/execution/ResultNodesInfo.java
index afc366f6be..9e8b3cfec5 100644
--- a/src/main/java/graphql/execution/ResultNodesInfo.java
+++ b/src/main/java/graphql/execution/ResultNodesInfo.java
@@ -2,6 +2,7 @@
import graphql.Internal;
import graphql.PublicApi;
+import org.jspecify.annotations.NullMarked;
import java.util.concurrent.atomic.AtomicInteger;
@@ -14,6 +15,7 @@
*
*/
@PublicApi
+@NullMarked
public class ResultNodesInfo {
public static final String MAX_RESULT_NODES = "__MAX_RESULT_NODES";
diff --git a/src/main/java/graphql/execution/ResultPath.java b/src/main/java/graphql/execution/ResultPath.java
index 696bfbc7f1..6690084947 100644
--- a/src/main/java/graphql/execution/ResultPath.java
+++ b/src/main/java/graphql/execution/ResultPath.java
@@ -4,6 +4,8 @@
import graphql.AssertException;
import graphql.PublicApi;
import graphql.collect.ImmutableKit;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -21,6 +23,7 @@
* class represents that path as a series of segments.
*/
@PublicApi
+@NullMarked
public class ResultPath {
private static final ResultPath ROOT_PATH = new ResultPath();
@@ -33,8 +36,8 @@ public static ResultPath rootPath() {
return ROOT_PATH;
}
- private final ResultPath parent;
- private final Object segment;
+ private final @Nullable ResultPath parent;
+ private final @Nullable Object segment;
// hash is effective immutable but lazily initialized similar to the hash code of java.lang.String
private int hash;
@@ -85,7 +88,7 @@ public ResultPath getPathWithoutListEnd() {
if (segment instanceof String) {
return this;
}
- return parent;
+ return assertNotNull(parent, "parent should not be null for non-root path");
}
/**
@@ -104,18 +107,18 @@ public boolean isNamedSegment() {
public String getSegmentName() {
- return (String) segment;
+ return (String) assertNotNull(segment, "segment should not be null");
}
public int getSegmentIndex() {
- return (int) segment;
+ return (int) assertNotNull(segment, "segment should not be null");
}
public Object getSegmentValue() {
- return segment;
+ return assertNotNull(segment, "segment should not be null");
}
- public ResultPath getParent() {
+ public @Nullable ResultPath getParent() {
return parent;
}
@@ -201,7 +204,7 @@ public ResultPath segment(int segment) {
*
* @return a new path with the last segment dropped off
*/
- public ResultPath dropSegment() {
+ public @Nullable ResultPath dropSegment() {
if (this == rootPath()) {
return null;
}
@@ -218,7 +221,7 @@ public ResultPath dropSegment() {
*/
public ResultPath replaceSegment(int segment) {
assertTrue(!ROOT_PATH.equals(this), "You MUST not call this with the root path");
- return new ResultPath(parent, segment);
+ return new ResultPath(assertNotNull(parent, "parent should not be null"), segment);
}
/**
@@ -231,7 +234,7 @@ public ResultPath replaceSegment(int segment) {
*/
public ResultPath replaceSegment(String segment) {
assertTrue(!ROOT_PATH.equals(this), "You MUST not call this with the root path");
- return new ResultPath(parent, segment);
+ return new ResultPath(assertNotNull(parent, "parent should not be null"), segment);
}
@@ -258,12 +261,12 @@ public ResultPath append(ResultPath path) {
public ResultPath sibling(String siblingField) {
assertTrue(!ROOT_PATH.equals(this), "You MUST not call this with the root path");
- return new ResultPath(this.parent, siblingField);
+ return new ResultPath(assertNotNull(this.parent, "parent should not be null"), siblingField);
}
public ResultPath sibling(int siblingField) {
assertTrue(!ROOT_PATH.equals(this), "You MUST not call this with the root path");
- return new ResultPath(this.parent, siblingField);
+ return new ResultPath(assertNotNull(this.parent, "parent should not be null"), siblingField);
}
/**
@@ -275,7 +278,7 @@ public List toList() {
}
LinkedList list = new LinkedList<>();
ResultPath p = this;
- while (p.segment != null) {
+ while (p != null && p.segment != null) {
list.addFirst(p.segment);
p = p.parent;
}
@@ -291,7 +294,7 @@ public List getKeysOnly() {
}
LinkedList list = new LinkedList<>();
ResultPath p = this;
- while (p.segment != null) {
+ while (p != null && p.segment != null) {
if (p.segment instanceof String) {
list.addFirst((String) p.segment);
}
@@ -328,7 +331,7 @@ public boolean equals(Object o) {
ResultPath self = this;
ResultPath that = (ResultPath) o;
- while (self.segment != null && that.segment != null) {
+ while (self != null && self.segment != null && that != null && that.segment != null) {
if (!Objects.equals(self.segment, that.segment)) {
return false;
}
@@ -336,7 +339,7 @@ public boolean equals(Object o) {
that = that.parent;
}
- return self.isRootPath() && that.isRootPath();
+ return (self == null || self.isRootPath()) && (that == null || that.isRootPath());
}
@Override
diff --git a/src/main/java/graphql/execution/SimpleDataFetcherExceptionHandler.java b/src/main/java/graphql/execution/SimpleDataFetcherExceptionHandler.java
index 79e201a333..adfb4d0786 100644
--- a/src/main/java/graphql/execution/SimpleDataFetcherExceptionHandler.java
+++ b/src/main/java/graphql/execution/SimpleDataFetcherExceptionHandler.java
@@ -3,6 +3,7 @@
import graphql.ExceptionWhileDataFetching;
import graphql.PublicApi;
import graphql.language.SourceLocation;
+import org.jspecify.annotations.NullMarked;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
@@ -12,6 +13,7 @@
* into the error collection
*/
@PublicApi
+@NullMarked
public class SimpleDataFetcherExceptionHandler implements DataFetcherExceptionHandler {
static final SimpleDataFetcherExceptionHandler defaultImpl = new SimpleDataFetcherExceptionHandler();
diff --git a/src/main/java/graphql/execution/SubscriptionExecutionStrategy.java b/src/main/java/graphql/execution/SubscriptionExecutionStrategy.java
index f1e1a9a919..112ae47641 100644
--- a/src/main/java/graphql/execution/SubscriptionExecutionStrategy.java
+++ b/src/main/java/graphql/execution/SubscriptionExecutionStrategy.java
@@ -17,6 +17,8 @@
import graphql.language.Field;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLObjectType;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
import org.reactivestreams.FlowAdapters;
import org.reactivestreams.Publisher;
@@ -40,6 +42,7 @@
* See https://www.reactive-streams.org/
*/
@PublicApi
+@NullMarked
public class SubscriptionExecutionStrategy extends ExecutionStrategy {
/**
@@ -132,7 +135,7 @@ private CompletableFuture> createSourceEventStream(ExecutionCo
* @return a reactive streams {@link Publisher} always
*/
@SuppressWarnings("unchecked")
- private static Publisher mkReactivePublisher(Object publisherObj) {
+ private static @Nullable Publisher mkReactivePublisher(@Nullable Object publisherObj) {
if (publisherObj != null) {
if (publisherObj instanceof Publisher) {
return (Publisher) publisherObj;
@@ -182,7 +185,7 @@ private CompletableFuture executeSubscriptionEvent(ExecutionCon
executionContext.getDataLoaderDispatcherStrategy().subscriptionEventCompletionDone(newParameters.getDeferredCallContext());
CompletableFuture overallResult = fieldValueInfo
.getFieldValueFuture()
- .thenApply(val -> new ExecutionResultImpl(val, newParameters.getDeferredCallContext().getErrors()))
+ .thenApply(val -> new ExecutionResultImpl(val, Assert.assertNotNull(newParameters.getDeferredCallContext(), "deferredCallContext should not be null").getErrors()))
.thenApply(executionResult -> wrapWithRootFieldName(newParameters, executionResult));
// dispatch instrumentation so they can know about each subscription event
diff --git a/src/main/java/graphql/execution/UnknownOperationException.java b/src/main/java/graphql/execution/UnknownOperationException.java
index 6ef523c9b1..13f427dff6 100644
--- a/src/main/java/graphql/execution/UnknownOperationException.java
+++ b/src/main/java/graphql/execution/UnknownOperationException.java
@@ -6,6 +6,8 @@
import graphql.GraphQLException;
import graphql.PublicApi;
import graphql.language.SourceLocation;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
import java.util.List;
@@ -15,13 +17,14 @@
* contained in the GraphQL query.
*/
@PublicApi
+@NullMarked
public class UnknownOperationException extends GraphQLException implements GraphQLError {
public UnknownOperationException(String message) {
super(message);
}
@Override
- public List getLocations() {
+ public @Nullable List getLocations() {
return null;
}
diff --git a/src/main/java/graphql/execution/UnresolvedTypeException.java b/src/main/java/graphql/execution/UnresolvedTypeException.java
index 4a6aad47d6..f964affb26 100644
--- a/src/main/java/graphql/execution/UnresolvedTypeException.java
+++ b/src/main/java/graphql/execution/UnresolvedTypeException.java
@@ -5,12 +5,14 @@
import graphql.schema.GraphQLNamedOutputType;
import graphql.schema.GraphQLType;
import graphql.schema.GraphQLTypeUtil;
+import org.jspecify.annotations.NullMarked;
/**
* This is thrown if a {@link graphql.schema.TypeResolver} fails to give back a concrete type
* or provides a type that doesn't implement the given interface or union.
*/
@PublicApi
+@NullMarked
public class UnresolvedTypeException extends GraphQLException {
private final GraphQLNamedOutputType interfaceOrUnionType;
diff --git a/src/main/java/graphql/execution/conditional/ConditionalNodeDecision.java b/src/main/java/graphql/execution/conditional/ConditionalNodeDecision.java
index 69afc6bbc2..c747d709c1 100644
--- a/src/main/java/graphql/execution/conditional/ConditionalNodeDecision.java
+++ b/src/main/java/graphql/execution/conditional/ConditionalNodeDecision.java
@@ -1,6 +1,7 @@
package graphql.execution.conditional;
import graphql.ExperimentalApi;
+import org.jspecify.annotations.NullMarked;
/**
* This callback interface allows custom implementations to decide if a field is included in a query or not.
@@ -9,6 +10,7 @@
* decisions on whether fields are considered part of a query.
*/
@ExperimentalApi
+@NullMarked
public interface ConditionalNodeDecision {
/**
diff --git a/src/main/java/graphql/execution/directives/QueryAppliedDirective.java b/src/main/java/graphql/execution/directives/QueryAppliedDirective.java
index 84492143fd..e1a949bbce 100644
--- a/src/main/java/graphql/execution/directives/QueryAppliedDirective.java
+++ b/src/main/java/graphql/execution/directives/QueryAppliedDirective.java
@@ -7,7 +7,8 @@
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLDirective;
import graphql.schema.GraphqlTypeBuilder;
-import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;
import java.util.Collection;
@@ -33,13 +34,14 @@
* See https://graphql.org/learn/queries/#directives for more details on the concept.
*/
@PublicApi
+@NullMarked
public class QueryAppliedDirective {
private final String name;
private final ImmutableList arguments;
- private final Directive definition;
+ private final @Nullable Directive definition;
- private QueryAppliedDirective(String name, Directive definition, Collection arguments) {
+ private QueryAppliedDirective(String name, @Nullable Directive definition, Collection arguments) {
assertValidName(name);
assertNotNull(arguments, "arguments can't be null");
this.name = name;
@@ -47,13 +49,11 @@ private QueryAppliedDirective(String name, Directive definition, Collection getArguments() {
return arguments;
}
- @Nullable
- public QueryAppliedDirectiveArgument getArgument(String name) {
+ public @Nullable QueryAppliedDirectiveArgument getArgument(String name) {
for (QueryAppliedDirectiveArgument argument : arguments) {
if (argument.getName().equals(name)) {
return argument;
@@ -71,8 +70,7 @@ public QueryAppliedDirectiveArgument getArgument(String name) {
return null;
}
- @Nullable
- public Directive getDefinition() {
+ public @Nullable Directive getDefinition() {
return definition;
}
@@ -107,6 +105,7 @@ public static Builder newDirective(QueryAppliedDirective existing) {
return new Builder(existing);
}
+ @NullUnmarked
public static class Builder extends GraphqlTypeBuilder {
private final Map arguments = new LinkedHashMap<>();
diff --git a/src/main/java/graphql/execution/directives/QueryAppliedDirectiveArgument.java b/src/main/java/graphql/execution/directives/QueryAppliedDirectiveArgument.java
index 8b772e91f7..6a9e58f326 100644
--- a/src/main/java/graphql/execution/directives/QueryAppliedDirectiveArgument.java
+++ b/src/main/java/graphql/execution/directives/QueryAppliedDirectiveArgument.java
@@ -10,7 +10,8 @@
import graphql.schema.GraphQLInputType;
import graphql.schema.GraphqlTypeBuilder;
import graphql.schema.InputValueWithState;
-import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;
import java.util.Locale;
@@ -26,19 +27,20 @@
* You can think of them as 'instances' of {@link GraphQLArgument}, when applied to a directive on a query element
*/
@PublicApi
+@NullMarked
public class QueryAppliedDirectiveArgument {
private final String name;
private final InputValueWithState value;
private final GraphQLInputType originalType;
- private final Argument definition;
+ private final @Nullable Argument definition;
private QueryAppliedDirectiveArgument(String name,
InputValueWithState value,
GraphQLInputType type,
- Argument definition
+ @Nullable Argument definition
) {
assertValidName(name);
this.name = name;
@@ -47,12 +49,10 @@ private QueryAppliedDirectiveArgument(String name,
this.definition = definition;
}
- @NonNull
public String getName() {
return name;
}
- @NonNull
public GraphQLInputType getType() {
return originalType;
}
@@ -64,7 +64,7 @@ public boolean hasSetValue() {
/**
* @return an input value with state for an applied directive argument
*/
- public @NonNull InputValueWithState getArgumentValue() {
+ public InputValueWithState getArgumentValue() {
return value;
}
@@ -83,8 +83,7 @@ public boolean hasSetValue() {
*
* @return a value of type T which is the java value of the argument
*/
- @Nullable
- public T getValue() {
+ public @Nullable T getValue() {
return getInputValueImpl(getType(), value, GraphQLContext.getDefault(), Locale.getDefault());
}
@@ -97,8 +96,7 @@ public T getValue() {
return null;
}
- @Nullable
- public Argument getDefinition() {
+ public @Nullable Argument getDefinition() {
return definition;
}
@@ -134,6 +132,7 @@ public String toString() {
'}';
}
+ @NullUnmarked
public static class Builder extends GraphqlTypeBuilder {
private InputValueWithState value = InputValueWithState.NOT_SET;
@@ -166,7 +165,7 @@ public Builder definition(Argument definition) {
*
* @return this builder
*/
- public Builder valueLiteral(@NonNull Value> value) {
+ public Builder valueLiteral(Value> value) {
this.value = InputValueWithState.newLiteralValue(value);
return this;
}
@@ -181,7 +180,7 @@ public Builder valueProgrammatic(@Nullable Object value) {
return this;
}
- public Builder inputValueWithState(@NonNull InputValueWithState value) {
+ public Builder inputValueWithState(InputValueWithState value) {
this.value = Assert.assertNotNull(value);
return this;
}
diff --git a/src/main/java/graphql/execution/directives/QueryDirectives.java b/src/main/java/graphql/execution/directives/QueryDirectives.java
index 6eee9f9323..d9bc5a1073 100644
--- a/src/main/java/graphql/execution/directives/QueryDirectives.java
+++ b/src/main/java/graphql/execution/directives/QueryDirectives.java
@@ -9,6 +9,8 @@
import graphql.normalized.NormalizedInputValue;
import graphql.schema.GraphQLDirective;
import graphql.schema.GraphQLSchema;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.NullUnmarked;
import java.util.List;
import java.util.Locale;
@@ -31,6 +33,7 @@
* @see graphql.execution.MergedField
*/
@PublicApi
+@NullMarked
public interface QueryDirectives {
/**
@@ -111,6 +114,7 @@ static Builder newQueryDirectives() {
return new QueryDirectivesBuilder();
}
+ @NullUnmarked
interface Builder {
Builder schema(GraphQLSchema schema);
diff --git a/src/main/java/graphql/language/Comment.java b/src/main/java/graphql/language/Comment.java
index a7a546facf..65096c6782 100644
--- a/src/main/java/graphql/language/Comment.java
+++ b/src/main/java/graphql/language/Comment.java
@@ -1,6 +1,8 @@
package graphql.language;
import graphql.PublicApi;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
import java.io.Serializable;
@@ -8,11 +10,12 @@
* A single-line comment. These are comments that start with a {@code #} in source documents.
*/
@PublicApi
+@NullMarked
public class Comment implements Serializable {
public final String content;
- public final SourceLocation sourceLocation;
+ public final @Nullable SourceLocation sourceLocation;
- public Comment(String content, SourceLocation sourceLocation) {
+ public Comment(String content, @Nullable SourceLocation sourceLocation) {
this.content = content;
this.sourceLocation = sourceLocation;
}
@@ -21,7 +24,7 @@ public String getContent() {
return content;
}
- public SourceLocation getSourceLocation() {
+ public @Nullable SourceLocation getSourceLocation() {
return sourceLocation;
}
}
diff --git a/src/main/java/graphql/language/Definition.java b/src/main/java/graphql/language/Definition.java
index f0e7d74dc9..5402bfe3c6 100644
--- a/src/main/java/graphql/language/Definition.java
+++ b/src/main/java/graphql/language/Definition.java
@@ -2,8 +2,10 @@
import graphql.PublicApi;
+import org.jspecify.annotations.NullMarked;
@PublicApi
+@NullMarked
public interface Definition extends Node {
}
diff --git a/src/main/java/graphql/language/IgnoredChar.java b/src/main/java/graphql/language/IgnoredChar.java
index eaa1689d0c..d316a4d6b1 100644
--- a/src/main/java/graphql/language/IgnoredChar.java
+++ b/src/main/java/graphql/language/IgnoredChar.java
@@ -1,6 +1,8 @@
package graphql.language;
import graphql.PublicApi;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
import java.io.Serializable;
import java.util.Objects;
@@ -12,6 +14,7 @@
* This costs more memory but for certain use cases (like editors) this maybe be useful
*/
@PublicApi
+@NullMarked
public class IgnoredChar implements Serializable {
public enum IgnoredCharKind {
@@ -51,7 +54,7 @@ public String toString() {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
diff --git a/src/main/java/graphql/language/IgnoredChars.java b/src/main/java/graphql/language/IgnoredChars.java
index ce3a1ad59c..241b4cc744 100644
--- a/src/main/java/graphql/language/IgnoredChars.java
+++ b/src/main/java/graphql/language/IgnoredChars.java
@@ -3,6 +3,7 @@
import com.google.common.collect.ImmutableList;
import graphql.PublicApi;
import graphql.collect.ImmutableKit;
+import org.jspecify.annotations.NullMarked;
import java.io.Serializable;
import java.util.List;
@@ -14,6 +15,7 @@
* This costs more memory but for certain use cases (like editors) this maybe be useful
*/
@PublicApi
+@NullMarked
public class IgnoredChars implements Serializable {
private final ImmutableList left;
diff --git a/src/main/java/graphql/language/Node.java b/src/main/java/graphql/language/Node.java
index 962934d76b..917594b876 100644
--- a/src/main/java/graphql/language/Node.java
+++ b/src/main/java/graphql/language/Node.java
@@ -4,6 +4,7 @@
import graphql.PublicApi;
import graphql.util.TraversalControl;
import graphql.util.TraverserContext;
+import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.io.Serializable;
@@ -21,6 +22,7 @@
* Every Node is immutable
*/
@PublicApi
+@NullMarked
public interface Node extends Serializable {
/**
diff --git a/src/main/java/graphql/language/NodeChildrenContainer.java b/src/main/java/graphql/language/NodeChildrenContainer.java
index d2e1b06fea..a986ebca76 100644
--- a/src/main/java/graphql/language/NodeChildrenContainer.java
+++ b/src/main/java/graphql/language/NodeChildrenContainer.java
@@ -1,6 +1,9 @@
package graphql.language;
import graphql.PublicApi;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.NullUnmarked;
+import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -15,6 +18,7 @@
* Container of children of a {@link Node}.
*/
@PublicApi
+@NullMarked
public class NodeChildrenContainer {
private final Map> children = new LinkedHashMap<>();
@@ -27,7 +31,7 @@ public List getChildren(String key) {
return (List) children.getOrDefault(key, emptyList());
}
- public T getChildOrNull(String key) {
+ public @Nullable T getChildOrNull(String key) {
List extends Node> result = children.getOrDefault(key, emptyList());
if (result.size() > 1) {
throw new IllegalStateException("children " + key + " is not a single value");
@@ -61,6 +65,7 @@ public boolean isEmpty() {
return this.children.isEmpty();
}
+ @NullUnmarked
public static class Builder {
private final Map> children = new LinkedHashMap<>();
diff --git a/src/main/java/graphql/language/NodeVisitor.java b/src/main/java/graphql/language/NodeVisitor.java
index 2ed79570b3..ca40709f5d 100644
--- a/src/main/java/graphql/language/NodeVisitor.java
+++ b/src/main/java/graphql/language/NodeVisitor.java
@@ -3,11 +3,13 @@
import graphql.PublicApi;
import graphql.util.TraversalControl;
import graphql.util.TraverserContext;
+import org.jspecify.annotations.NullMarked;
/**
* Used by {@link NodeTraverser} to visit {@link Node}.
*/
@PublicApi
+@NullMarked
public interface NodeVisitor {
TraversalControl visitArgument(Argument node, TraverserContext data);
diff --git a/src/main/java/graphql/language/NodeVisitorStub.java b/src/main/java/graphql/language/NodeVisitorStub.java
index f0fcd6b3fd..b5d00073c4 100644
--- a/src/main/java/graphql/language/NodeVisitorStub.java
+++ b/src/main/java/graphql/language/NodeVisitorStub.java
@@ -3,11 +3,13 @@
import graphql.PublicApi;
import graphql.util.TraversalControl;
import graphql.util.TraverserContext;
+import org.jspecify.annotations.NullMarked;
/**
* Convenient implementation of {@link NodeVisitor} for easy subclassing methods handling different types of Nodes in one method.
*/
@PublicApi
+@NullMarked
public class NodeVisitorStub implements NodeVisitor {
@Override
public TraversalControl visitArgument(Argument node, TraverserContext context) {
diff --git a/src/main/java/graphql/language/SourceLocation.java b/src/main/java/graphql/language/SourceLocation.java
index a4ae90a039..b97cc103ac 100644
--- a/src/main/java/graphql/language/SourceLocation.java
+++ b/src/main/java/graphql/language/SourceLocation.java
@@ -7,24 +7,27 @@
import graphql.schema.GraphQLSchemaElement;
import graphql.schema.GraphQLTypeUtil;
import graphql.schema.idl.SchemaGenerator;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
import java.io.Serializable;
import java.util.Objects;
@PublicApi
+@NullMarked
public class SourceLocation implements Serializable {
public static final SourceLocation EMPTY = new SourceLocation(-1, -1);
private final int line;
private final int column;
- private final String sourceName;
+ private final @Nullable String sourceName;
public SourceLocation(int line, int column) {
this(line, column, null);
}
- public SourceLocation(int line, int column, String sourceName) {
+ public SourceLocation(int line, int column, @Nullable String sourceName) {
this.line = line;
this.column = column;
this.sourceName = sourceName;
@@ -38,12 +41,12 @@ public int getColumn() {
return column;
}
- public String getSourceName() {
+ public @Nullable String getSourceName() {
return sourceName;
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
@@ -91,7 +94,7 @@ public String toString() {
*
* @return the source location if available or null if it's not.
*/
- public static SourceLocation getLocation(GraphQLSchemaElement schemaElement) {
+ public static @Nullable SourceLocation getLocation(GraphQLSchemaElement schemaElement) {
if (schemaElement instanceof GraphQLModifiedType) {
schemaElement = GraphQLTypeUtil.unwrapAllAs((GraphQLModifiedType) schemaElement);
}
diff --git a/src/main/java/graphql/language/Type.java b/src/main/java/graphql/language/Type.java
index a3141e56d0..85d06564de 100644
--- a/src/main/java/graphql/language/Type.java
+++ b/src/main/java/graphql/language/Type.java
@@ -2,8 +2,10 @@
import graphql.PublicApi;
+import org.jspecify.annotations.NullMarked;
@PublicApi
+@NullMarked
public interface Type extends Node {
}
diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy
index d70f003b27..e9ffa25b5a 100644
--- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy
+++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy
@@ -48,16 +48,6 @@ class JSpecifyAnnotationsCheck extends Specification {
"graphql.execution.NormalizedVariables",
"graphql.execution.OneOfNullValueException",
"graphql.execution.OneOfTooManyKeysException",
- "graphql.execution.ResultNodesInfo",
- "graphql.execution.ResultPath",
- "graphql.execution.SimpleDataFetcherExceptionHandler",
- "graphql.execution.SubscriptionExecutionStrategy",
- "graphql.execution.UnknownOperationException",
- "graphql.execution.UnresolvedTypeException",
- "graphql.execution.conditional.ConditionalNodeDecision",
- "graphql.execution.directives.QueryAppliedDirective",
- "graphql.execution.directives.QueryAppliedDirectiveArgument",
- "graphql.execution.directives.QueryDirectives",
"graphql.execution.incremental.DeferredExecution",
"graphql.execution.instrumentation.ChainedInstrumentation",
"graphql.execution.instrumentation.DocumentAndVariables",
@@ -109,8 +99,6 @@ class JSpecifyAnnotationsCheck extends Specification {
"graphql.language.AstSignature",
"graphql.language.AstSorter",
"graphql.language.AstTransformer",
- "graphql.language.Comment",
- "graphql.language.Definition",
"graphql.language.DescribedNode",
"graphql.language.Description",
"graphql.language.Directive",
@@ -125,8 +113,6 @@ class JSpecifyAnnotationsCheck extends Specification {
"graphql.language.FieldDefinition",
"graphql.language.FragmentDefinition",
"graphql.language.FragmentSpread",
- "graphql.language.IgnoredChar",
- "graphql.language.IgnoredChars",
"graphql.language.ImplementingTypeDefinition",
"graphql.language.InlineFragment",
"graphql.language.InputObjectTypeDefinition",
@@ -135,13 +121,9 @@ class JSpecifyAnnotationsCheck extends Specification {
"graphql.language.InterfaceTypeDefinition",
"graphql.language.InterfaceTypeExtensionDefinition",
"graphql.language.ListType",
- "graphql.language.Node",
- "graphql.language.NodeChildrenContainer",
"graphql.language.NodeDirectivesBuilder",
"graphql.language.NodeParentTree",
"graphql.language.NodeTraverser",
- "graphql.language.NodeVisitor",
- "graphql.language.NodeVisitorStub",
"graphql.language.NonNullType",
"graphql.language.ObjectField",
"graphql.language.ObjectTypeDefinition",
@@ -159,8 +141,6 @@ class JSpecifyAnnotationsCheck extends Specification {
"graphql.language.Selection",
"graphql.language.SelectionSet",
"graphql.language.SelectionSetContainer",
- "graphql.language.SourceLocation",
- "graphql.language.Type",
"graphql.language.TypeDefinition",
"graphql.language.TypeKind",
"graphql.language.TypeName",