From 1b01d2be7b93674fb7d7057f8752aa5c2acaeb70 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 21 Aug 2025 19:23:43 +1000 Subject: [PATCH 01/82] Annotate GraphQLAppliedDirective --- .../java/graphql/schema/GraphQLAppliedDirective.java | 9 +++++++-- .../graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/schema/GraphQLAppliedDirective.java b/src/main/java/graphql/schema/GraphQLAppliedDirective.java index ba73f99701..54104e7f7b 100644 --- a/src/main/java/graphql/schema/GraphQLAppliedDirective.java +++ b/src/main/java/graphql/schema/GraphQLAppliedDirective.java @@ -6,6 +6,9 @@ import graphql.language.Directive; import graphql.util.TraversalControl; import graphql.util.TraverserContext; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import java.util.LinkedHashMap; import java.util.List; @@ -31,6 +34,7 @@ * See https://graphql.org/learn/queries/#directives for more details on the concept. */ @PublicApi +@NullMarked public class GraphQLAppliedDirective implements GraphQLNamedSchemaElement { private final String name; @@ -53,7 +57,7 @@ public String getName() { } @Override - public String getDescription() { + public @Nullable String getDescription() { return null; } @@ -61,7 +65,7 @@ public List getArguments() { return arguments; } - public GraphQLAppliedDirectiveArgument getArgument(String name) { + public @Nullable GraphQLAppliedDirectiveArgument getArgument(String name) { for (GraphQLAppliedDirectiveArgument argument : arguments) { if (argument.getName().equals(name)) { return argument; @@ -152,6 +156,7 @@ public static Builder newDirective(GraphQLAppliedDirective existing) { return new Builder(existing); } + @NullUnmarked public static class Builder extends GraphqlTypeBuilder { private final Map arguments = new LinkedHashMap<>(); diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 95dc0cc435..791c68af49 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -227,7 +227,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.schema.DefaultGraphqlTypeComparatorRegistry", "graphql.schema.DelegatingDataFetchingEnvironment", "graphql.schema.FieldCoordinates", - "graphql.schema.GraphQLAppliedDirective", "graphql.schema.GraphQLAppliedDirectiveArgument", "graphql.schema.GraphQLArgument", "graphql.schema.GraphQLCodeRegistry", From 8684b480c4d26354f5018b256679d72017fcf04c Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 21 Aug 2025 19:51:56 +1000 Subject: [PATCH 02/82] Add GraphQLSchema annotation --- .../java/graphql/schema/GraphQLSchema.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/graphql/schema/GraphQLSchema.java b/src/main/java/graphql/schema/GraphQLSchema.java index 5b480810c9..ed7912c802 100644 --- a/src/main/java/graphql/schema/GraphQLSchema.java +++ b/src/main/java/graphql/schema/GraphQLSchema.java @@ -18,7 +18,8 @@ import graphql.schema.validation.InvalidSchemaException; import graphql.schema.validation.SchemaValidationError; import graphql.schema.validation.SchemaValidator; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import java.util.ArrayList; @@ -46,6 +47,7 @@ * See https://graphql.org/learn/schema/#type-language for more details */ @PublicApi +@NullMarked public class GraphQLSchema { private final GraphQLObjectType queryType; @@ -63,7 +65,7 @@ public class GraphQLSchema { private final SchemaDefinition definition; private final ImmutableList extensionDefinitions; private final String description; - private final GraphQLCodeRegistry codeRegistry; + private final @Nullable GraphQLCodeRegistry codeRegistry; private final ImmutableMap typeMap; private final ImmutableMap> interfaceNameToObjectTypes; @@ -190,7 +192,7 @@ private static ImmutableMap> buildInterfacesToObje return map.build(); } - public GraphQLCodeRegistry getCodeRegistry() { + public @Nullable GraphQLCodeRegistry getCodeRegistry() { return codeRegistry; } @@ -230,7 +232,7 @@ public Set getAdditionalTypes() { * * @return the type */ - public @Nullable GraphQLType getType(@NonNull String typeName) { + public @Nullable GraphQLType getType(String typeName) { return typeMap.get(typeName); } @@ -262,7 +264,7 @@ public List getTypes(Collection typeNames) { * * @return the type cast to the target type. */ - public T getTypeAs(String typeName) { + public @Nullable T getTypeAs(String typeName) { //noinspection unchecked return (T) typeMap.get(typeName); } @@ -287,7 +289,7 @@ public boolean containsType(String typeName) { * * @throws graphql.GraphQLException if the type is NOT an object type */ - public GraphQLObjectType getObjectType(String typeName) { + public @Nullable GraphQLObjectType getObjectType(String typeName) { GraphQLType graphQLType = typeMap.get(typeName); if (graphQLType != null) { assertTrue(graphQLType instanceof GraphQLObjectType, @@ -304,7 +306,7 @@ public GraphQLObjectType getObjectType(String typeName) { * * @return the field or null if it does not exist */ - public GraphQLFieldDefinition getFieldDefinition(FieldCoordinates fieldCoordinates) { + public @Nullable GraphQLFieldDefinition getFieldDefinition(FieldCoordinates fieldCoordinates) { String fieldName = fieldCoordinates.getFieldName(); if (fieldCoordinates.isSystemCoordinates()) { if (fieldName.equals(this.getIntrospectionSchemaFieldDefinition().getName())) { @@ -365,7 +367,7 @@ public List getAllElementsAsList() { * * @return list of types implementing provided interface */ - public List getImplementations(GraphQLInterfaceType type) { + public @Nullable List getImplementations(GraphQLInterfaceType type) { return interfaceNameToObjectTypes.getOrDefault(type.getName(), emptyList()); } @@ -642,6 +644,7 @@ public static Builder newSchema(GraphQLSchema existingSchema) { .description(existingSchema.getDescription()); } + @NullUnmarked public static class BuilderWithoutTypes { private GraphQLCodeRegistry codeRegistry; private String description; @@ -672,6 +675,7 @@ public GraphQLSchema build() { } } + @NullUnmarked public static class Builder { private GraphQLObjectType queryType; private GraphQLObjectType mutationType; @@ -752,7 +756,6 @@ public Builder clearDirectives() { return this; } - public Builder withSchemaDirectives(GraphQLDirective... directives) { for (GraphQLDirective directive : directives) { withSchemaDirective(directive); From 0cdc8de9217781de563ecd46c3fff5e2adae5bb2 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 21 Aug 2025 19:52:07 +1000 Subject: [PATCH 03/82] Remove exemption --- src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 791c68af49..1e4430cb84 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -256,7 +256,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.schema.GraphQLObjectType", "graphql.schema.GraphQLOutputType", "graphql.schema.GraphQLScalarType", - "graphql.schema.GraphQLSchema", "graphql.schema.GraphQLSchemaElement", "graphql.schema.GraphQLType", "graphql.schema.GraphQLTypeReference", From 30d0abe5030860e27a7e15f3a1de7341d2cc12bc Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 21 Aug 2025 19:59:11 +1000 Subject: [PATCH 04/82] Annotate GraphQLType and GraphQLEnumType --- src/main/java/graphql/schema/GraphQLEnumType.java | 8 +++++--- src/main/java/graphql/schema/GraphQLType.java | 2 ++ .../graphql/archunit/JSpecifyAnnotationsCheck.groovy | 2 -- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/graphql/schema/GraphQLEnumType.java b/src/main/java/graphql/schema/GraphQLEnumType.java index 856907315d..0061d2b810 100644 --- a/src/main/java/graphql/schema/GraphQLEnumType.java +++ b/src/main/java/graphql/schema/GraphQLEnumType.java @@ -13,7 +13,8 @@ import graphql.util.FpKit; import graphql.util.TraversalControl; import graphql.util.TraverserContext; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -141,7 +142,7 @@ public List getValues() { return ImmutableList.copyOf(valueDefinitionMap.values()); } - public GraphQLEnumValueDefinition getValue(String name) { + public @Nullable GraphQLEnumValueDefinition getValue(String name) { return valueDefinitionMap.get(name); } @@ -150,7 +151,7 @@ private ImmutableMap buildMap(List assertShouldNeverHappen("Duplicated definition for field '%s' in type '%s'", fld1.getName(), this.name))); } - private Object getValueByName(@NonNull Object value, GraphQLContext graphQLContext, Locale locale) { + private Object getValueByName(Object value, GraphQLContext graphQLContext, Locale locale) { GraphQLEnumValueDefinition enumValueDefinition = valueDefinitionMap.get(value.toString()); if (enumValueDefinition != null) { return enumValueDefinition.getValue(); @@ -324,6 +325,7 @@ public static Builder newEnum(GraphQLEnumType existing) { return new Builder(existing); } + @NullUnmarked public static class Builder extends GraphqlDirectivesContainerTypeBuilder { private EnumTypeDefinition definition; diff --git a/src/main/java/graphql/schema/GraphQLType.java b/src/main/java/graphql/schema/GraphQLType.java index e47ed46a2e..a11099a86b 100644 --- a/src/main/java/graphql/schema/GraphQLType.java +++ b/src/main/java/graphql/schema/GraphQLType.java @@ -2,6 +2,7 @@ import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; /** * A type inside the GraphQLSchema. A type doesn't have to have name, e.g. {@link GraphQLList}. @@ -9,5 +10,6 @@ * See {@link GraphQLNamedType} for types with a name. */ @PublicApi +@NullMarked public interface GraphQLType extends GraphQLSchemaElement { } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 1e4430cb84..da730aa5ca 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -233,7 +233,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.schema.GraphQLCompositeType", "graphql.schema.GraphQLDirective", "graphql.schema.GraphQLDirectiveContainer", - "graphql.schema.GraphQLEnumType", "graphql.schema.GraphQLEnumValueDefinition", "graphql.schema.GraphQLFieldDefinition", "graphql.schema.GraphQLFieldsContainer", @@ -257,7 +256,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.schema.GraphQLOutputType", "graphql.schema.GraphQLScalarType", "graphql.schema.GraphQLSchemaElement", - "graphql.schema.GraphQLType", "graphql.schema.GraphQLTypeReference", "graphql.schema.GraphQLTypeUtil", "graphql.schema.GraphQLTypeVisitor", From 6ee01b8d1bad802b13dfcc6a16757cfcba63b9d8 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 21 Aug 2025 20:00:06 +1000 Subject: [PATCH 05/82] Add Nullmarked to GraphQLEnumType --- src/main/java/graphql/schema/GraphQLEnumType.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/graphql/schema/GraphQLEnumType.java b/src/main/java/graphql/schema/GraphQLEnumType.java index 0061d2b810..1700b0b80d 100644 --- a/src/main/java/graphql/schema/GraphQLEnumType.java +++ b/src/main/java/graphql/schema/GraphQLEnumType.java @@ -13,6 +13,7 @@ import graphql.util.FpKit; import graphql.util.TraversalControl; import graphql.util.TraverserContext; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; @@ -41,6 +42,7 @@ * See https://graphql.org/learn/schema/#enumeration-types for more details */ @PublicApi +@NullMarked public class GraphQLEnumType implements GraphQLNamedInputType, GraphQLNamedOutputType, GraphQLUnmodifiedType, GraphQLNullableType, GraphQLDirectiveContainer { private final String name; From a45c613666a563aa74e5890adcccb415de92f74b Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 21 Aug 2025 20:05:23 +1000 Subject: [PATCH 06/82] Annotate GraphQLCodeRegistry --- src/main/java/graphql/schema/GraphQLCodeRegistry.java | 4 ++++ .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/GraphQLCodeRegistry.java b/src/main/java/graphql/schema/GraphQLCodeRegistry.java index 26574b819c..340c6f0e80 100644 --- a/src/main/java/graphql/schema/GraphQLCodeRegistry.java +++ b/src/main/java/graphql/schema/GraphQLCodeRegistry.java @@ -4,6 +4,8 @@ import graphql.Internal; import graphql.PublicApi; import graphql.schema.visibility.GraphqlFieldVisibility; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; import java.util.HashMap; import java.util.LinkedHashMap; @@ -26,6 +28,7 @@ * removed the type system objects will be able have proper hashCode/equals methods and be checked for proper equality. */ @PublicApi +@NullMarked public class GraphQLCodeRegistry { private final Map> dataFetcherMap; @@ -191,6 +194,7 @@ public static Builder newCodeRegistry(GraphQLCodeRegistry existingCodeRegistry) return new Builder(existingCodeRegistry); } + @NullUnmarked public static class Builder { private final Map> dataFetcherMap = new LinkedHashMap<>(); private final Map> systemDataFetcherMap = new LinkedHashMap<>(); diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index da730aa5ca..433020b19f 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -229,7 +229,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.schema.FieldCoordinates", "graphql.schema.GraphQLAppliedDirectiveArgument", "graphql.schema.GraphQLArgument", - "graphql.schema.GraphQLCodeRegistry", "graphql.schema.GraphQLCompositeType", "graphql.schema.GraphQLDirective", "graphql.schema.GraphQLDirectiveContainer", From 70336d52fff03a401344dd803dff5dc7cddf12cc Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 21 Aug 2025 20:09:09 +1000 Subject: [PATCH 07/82] Annotate GraphQLList --- src/main/java/graphql/schema/GraphQLList.java | 9 +++++---- .../graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/graphql/schema/GraphQLList.java b/src/main/java/graphql/schema/GraphQLList.java index 1ac94f5ffe..67792b8323 100644 --- a/src/main/java/graphql/schema/GraphQLList.java +++ b/src/main/java/graphql/schema/GraphQLList.java @@ -4,6 +4,8 @@ import graphql.PublicApi; import graphql.util.TraversalControl; import graphql.util.TraverserContext; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Collections; import java.util.List; @@ -17,15 +19,14 @@ * See https://graphql.org/learn/schema/#lists-and-non-null for more details on the concept */ @PublicApi +@NullMarked public class GraphQLList implements GraphQLType, GraphQLInputType, GraphQLOutputType, GraphQLModifiedType, GraphQLNullableType { - private final GraphQLType originalWrappedType; - private GraphQLType replacedWrappedType; + private @Nullable GraphQLType replacedWrappedType; public static final String CHILD_WRAPPED_TYPE = "wrappedType"; - /** * A factory method for creating list types so that when used with static imports allows * more readable code such as @@ -60,7 +61,7 @@ void replaceType(GraphQLType type) { } - public boolean isEqualTo(Object o) { + public boolean isEqualTo(@Nullable Object o) { if (this == o) { return true; } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 433020b19f..a17f7651f0 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -243,7 +243,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.schema.GraphQLInputType", "graphql.schema.GraphQLInputValueDefinition", "graphql.schema.GraphQLInterfaceType", - "graphql.schema.GraphQLList", "graphql.schema.GraphQLModifiedType", "graphql.schema.GraphQLNamedInputType", "graphql.schema.GraphQLNamedOutputType", From 29f836314d2739a5dcd34877edeacffdd99c7d7b Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 21 Aug 2025 20:12:38 +1000 Subject: [PATCH 08/82] Annotate PropertyDataFetcher --- .../java/graphql/schema/PropertyDataFetcher.java | 15 +++++++++------ .../archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/graphql/schema/PropertyDataFetcher.java b/src/main/java/graphql/schema/PropertyDataFetcher.java index 38382f4da9..0ac9d107a0 100644 --- a/src/main/java/graphql/schema/PropertyDataFetcher.java +++ b/src/main/java/graphql/schema/PropertyDataFetcher.java @@ -3,6 +3,8 @@ import graphql.Assert; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -35,10 +37,11 @@ * @see graphql.schema.DataFetcher */ @PublicApi +@NullMarked public class PropertyDataFetcher implements LightDataFetcher { - private final String propertyName; - private final Function function; + private final @Nullable String propertyName; + private final @Nullable Function function; /** * This constructor will use the property name and examine the {@link DataFetchingEnvironment#getSource()} @@ -107,23 +110,23 @@ public static PropertyDataFetcher fetching(Function function) { /** * @return the property that this is fetching for */ - public String getPropertyName() { + public @Nullable String getPropertyName() { return propertyName; } @Override - public T get(GraphQLFieldDefinition fieldDefinition, Object source, Supplier environmentSupplier) throws Exception { + public @Nullable T get(GraphQLFieldDefinition fieldDefinition, Object source, Supplier environmentSupplier) throws Exception { return getImpl(source, fieldDefinition.getType(), environmentSupplier); } @Override - public T get(DataFetchingEnvironment environment) { + public @Nullable T get(DataFetchingEnvironment environment) { Object source = environment.getSource(); return getImpl(source, environment.getFieldType(), () -> environment); } @SuppressWarnings("unchecked") - private T getImpl(Object source, GraphQLOutputType fieldDefinition, Supplier environmentSupplier) { + private @Nullable T getImpl(@Nullable Object source, GraphQLOutputType fieldDefinition, Supplier environmentSupplier) { if (source == null) { return null; } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index a17f7651f0..cd22f25ce9 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -264,7 +264,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.schema.GraphqlTypeComparatorEnvironment", "graphql.schema.GraphqlTypeComparatorRegistry", "graphql.schema.InputValueWithState", - "graphql.schema.PropertyDataFetcher", "graphql.schema.SchemaElementChildrenContainer", "graphql.schema.SchemaTransformer", "graphql.schema.SchemaTraverser", From 083b2ab146875818b89ae7b4cd80cba98a526c3d Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 21 Aug 2025 20:16:34 +1000 Subject: [PATCH 09/82] Annotate GraphQLUnionType --- src/main/java/graphql/schema/GraphQLUnionType.java | 6 +++++- .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/schema/GraphQLUnionType.java b/src/main/java/graphql/schema/GraphQLUnionType.java index 23a7e9f195..60f17fee3f 100644 --- a/src/main/java/graphql/schema/GraphQLUnionType.java +++ b/src/main/java/graphql/schema/GraphQLUnionType.java @@ -16,7 +16,10 @@ import java.util.List; import java.util.Map; import java.util.function.Consumer; + +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import static graphql.Assert.assertNotEmpty; import static graphql.Assert.assertNotNull; @@ -36,6 +39,7 @@ * See https://graphql.org/learn/schema/#union-types for more details on the concept. */ @PublicApi +@NullMarked public class GraphQLUnionType implements GraphQLNamedOutputType, GraphQLCompositeType, GraphQLUnmodifiedType, GraphQLNullableType, GraphQLDirectiveContainer { private final String name; @@ -46,7 +50,7 @@ public class GraphQLUnionType implements GraphQLNamedOutputType, GraphQLComposit private final ImmutableList extensionDefinitions; private final DirectivesUtil.DirectivesHolder directives; - private ImmutableList replacedTypes; + private @Nullable ImmutableList replacedTypes; public static final String CHILD_TYPES = "types"; diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index cd22f25ce9..ed6d308a4c 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -258,7 +258,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.schema.GraphQLTypeUtil", "graphql.schema.GraphQLTypeVisitor", "graphql.schema.GraphQLTypeVisitorStub", - "graphql.schema.GraphQLUnionType", "graphql.schema.GraphQLUnmodifiedType", "graphql.schema.GraphqlElementParentTree", "graphql.schema.GraphqlTypeComparatorEnvironment", From b4761ef4f69a5f30e451185c1127a6ac22611395 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 21 Aug 2025 20:18:36 +1000 Subject: [PATCH 10/82] Tidy up --- src/main/java/graphql/schema/GraphQLEnumType.java | 2 +- src/main/java/graphql/schema/GraphQLType.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/schema/GraphQLEnumType.java b/src/main/java/graphql/schema/GraphQLEnumType.java index 1700b0b80d..717ad2f99b 100644 --- a/src/main/java/graphql/schema/GraphQLEnumType.java +++ b/src/main/java/graphql/schema/GraphQLEnumType.java @@ -135,7 +135,7 @@ public Value valueToLiteral(Object input, GraphQLContext graphQLContext, Loca GraphQLEnumValueDefinition enumValueDefinition = valueDefinitionMap.get(input.toString()); if (enumValueDefinition == null) { assertShouldNeverHappen(i18nMsg(locale, "Enum.badName", name, input.toString())); - }; + } return EnumValue.newEnumValue(enumValueDefinition.getName()).build(); } diff --git a/src/main/java/graphql/schema/GraphQLType.java b/src/main/java/graphql/schema/GraphQLType.java index a11099a86b..cb332f2f0c 100644 --- a/src/main/java/graphql/schema/GraphQLType.java +++ b/src/main/java/graphql/schema/GraphQLType.java @@ -6,7 +6,7 @@ /** * A type inside the GraphQLSchema. A type doesn't have to have name, e.g. {@link GraphQLList}. - * + *

* See {@link GraphQLNamedType} for types with a name. */ @PublicApi From 9a78fb3622a8320a13327c3391bdf5737f3730e1 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 21 Aug 2025 20:27:19 +1000 Subject: [PATCH 11/82] Annotate GraphQLScalarType --- .../graphql/schema/GraphQLScalarType.java | 22 ++++++++++--------- .../archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/java/graphql/schema/GraphQLScalarType.java b/src/main/java/graphql/schema/GraphQLScalarType.java index bf5442cda9..2c091e3f36 100644 --- a/src/main/java/graphql/schema/GraphQLScalarType.java +++ b/src/main/java/graphql/schema/GraphQLScalarType.java @@ -14,7 +14,10 @@ import java.util.List; import java.util.Map; import java.util.function.Consumer; + +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import static graphql.Assert.assertNotNull; import static graphql.Assert.assertValidName; @@ -29,7 +32,7 @@ * for example, a GraphQL system could define a scalar called Time which, while serialized as a string, promises to * conform to ISO‐8601. When querying a field of type Time, you can then rely on the ability to parse the result with an ISO‐8601 parser and use a client‐specific primitive for time. *

- * From the spec : https://spec.graphql.org/October2021/#sec-Scalars + * From the spec : ... * *

* graphql-java ships with a set of predefined scalar types via {@link graphql.Scalars} @@ -37,26 +40,26 @@ * @see graphql.Scalars */ @PublicApi -public class -GraphQLScalarType implements GraphQLNamedInputType, GraphQLNamedOutputType, GraphQLUnmodifiedType, GraphQLNullableType, GraphQLDirectiveContainer { +@NullMarked +public class GraphQLScalarType implements GraphQLNamedInputType, GraphQLNamedOutputType, GraphQLUnmodifiedType, GraphQLNullableType, GraphQLDirectiveContainer { private final String name; - private final String description; + private final @Nullable String description; private final Coercing coercing; private final ScalarTypeDefinition definition; private final ImmutableList extensionDefinitions; private final DirectivesUtil.DirectivesHolder directivesHolder; - private final String specifiedByUrl; + private final @Nullable String specifiedByUrl; @Internal private GraphQLScalarType(String name, - String description, + @Nullable String description, Coercing coercing, List directives, List appliedDirectives, ScalarTypeDefinition definition, List extensionDefinitions, - String specifiedByUrl) { + @Nullable String specifiedByUrl) { assertValidName(name); assertNotNull(coercing, () -> "coercing can't be null"); assertNotNull(directives, () -> "directives can't be null"); @@ -76,11 +79,11 @@ public String getName() { } - public String getDescription() { + public @Nullable String getDescription() { return description; } - public String getSpecifiedByUrl() { + public @Nullable String getSpecifiedByUrl() { return specifiedByUrl; } @@ -213,7 +216,6 @@ public static Builder newScalar(GraphQLScalarType existing) { return new Builder(existing); } - @PublicApi @NullUnmarked public static class Builder extends GraphqlDirectivesContainerTypeBuilder { diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index ed6d308a4c..f602807076 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -252,7 +252,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.schema.GraphQLNullableType", "graphql.schema.GraphQLObjectType", "graphql.schema.GraphQLOutputType", - "graphql.schema.GraphQLScalarType", "graphql.schema.GraphQLSchemaElement", "graphql.schema.GraphQLTypeReference", "graphql.schema.GraphQLTypeUtil", From 7bbfd9c62f2ae3fcfa5b6495906c2015c969a9f2 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 21 Aug 2025 20:28:50 +1000 Subject: [PATCH 12/82] Annotate GraphQLNamedInputType --- src/main/java/graphql/schema/GraphQLNamedInputType.java | 2 ++ .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/GraphQLNamedInputType.java b/src/main/java/graphql/schema/GraphQLNamedInputType.java index d44aeb7bab..493242b7bc 100644 --- a/src/main/java/graphql/schema/GraphQLNamedInputType.java +++ b/src/main/java/graphql/schema/GraphQLNamedInputType.java @@ -2,11 +2,13 @@ import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; /** * Input types represent those set of types that are allowed to be accepted as graphql mutation input, as opposed * to {@link GraphQLOutputType}s which can only be used as graphql response output. */ @PublicApi +@NullMarked public interface GraphQLNamedInputType extends GraphQLInputType, GraphQLNamedType { } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index f602807076..7e684a58a8 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -244,7 +244,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.schema.GraphQLInputValueDefinition", "graphql.schema.GraphQLInterfaceType", "graphql.schema.GraphQLModifiedType", - "graphql.schema.GraphQLNamedInputType", "graphql.schema.GraphQLNamedOutputType", "graphql.schema.GraphQLNamedSchemaElement", "graphql.schema.GraphQLNamedType", From 7427f644997554179cb3b675932b7be57fdb6e32 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 23 Aug 2025 16:10:17 +0200 Subject: [PATCH 13/82] Add ErrorType --- src/main/java/graphql/ErrorType.java | 3 +++ src/main/java/graphql/GraphQLError.java | 6 +++--- .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 3 +-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/graphql/ErrorType.java b/src/main/java/graphql/ErrorType.java index 9adee6d461..0e8d85cfcf 100644 --- a/src/main/java/graphql/ErrorType.java +++ b/src/main/java/graphql/ErrorType.java @@ -1,10 +1,13 @@ package graphql; +import org.jspecify.annotations.NullMarked; + /** * All the errors in graphql belong to one of these categories */ @PublicApi +@NullMarked public enum ErrorType implements ErrorClassification { InvalidSyntax, ValidationError, diff --git a/src/main/java/graphql/GraphQLError.java b/src/main/java/graphql/GraphQLError.java index 90c3527b50..5f4e0412d0 100644 --- a/src/main/java/graphql/GraphQLError.java +++ b/src/main/java/graphql/GraphQLError.java @@ -11,7 +11,7 @@ /** * The interface describing graphql errors - * + *

* NOTE: This class implements {@link java.io.Serializable} and hence it can be serialised and placed into a distributed cache. However we * are not aiming to provide long term compatibility and do not intend for you to place this serialised data into permanent storage, * with times frames that cross graphql-java versions. While we don't change things unnecessarily, we may inadvertently break @@ -42,7 +42,7 @@ public interface GraphQLError extends Serializable { * The graphql spec says that the (optional) path field of any error must be * a list of path entries starting at the root of the response * and ending with the field associated with the error - * https://spec.graphql.org/draft/#sec-Errors.Error-Result-Format + * ... * * @return the path in list format */ @@ -54,7 +54,7 @@ default List getPath() { * The graphql specification says that result of a call should be a map that follows certain rules on what items * should be present. Certain JSON serializers may or may interpret the error to spec, so this method * is provided to produce a map that strictly follows the specification. - * + *

* See : GraphQL Spec - 7.1.2 Errors * * @return a map of the error that strictly follows the specification diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 7e684a58a8..9cc2a047b1 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -1,4 +1,4 @@ -package graphql +package graphql.archunit import com.tngtech.archunit.core.importer.ClassFileImporter import com.tngtech.archunit.core.importer.ImportOption @@ -14,7 +14,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.AssertException", "graphql.Directives", "graphql.ErrorClassification", - "graphql.ErrorType", "graphql.ExceptionWhileDataFetching", "graphql.ExecutionResult", "graphql.GraphQLContext", From 120095c14f032ac83c77d13ca3853abe97acdccd Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 23 Aug 2025 16:13:03 +0200 Subject: [PATCH 14/82] Annotate Directives --- src/main/java/graphql/Directives.java | 2 ++ .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/Directives.java b/src/main/java/graphql/Directives.java index 37f2b28550..e27c738747 100644 --- a/src/main/java/graphql/Directives.java +++ b/src/main/java/graphql/Directives.java @@ -6,6 +6,7 @@ import graphql.language.DirectiveDefinition; import graphql.language.StringValue; import graphql.schema.GraphQLDirective; +import org.jspecify.annotations.NullMarked; import java.util.concurrent.atomic.AtomicBoolean; @@ -34,6 +35,7 @@ * The directives that are understood by graphql-java */ @PublicApi +@NullMarked public class Directives { private static final String DEPRECATED = "deprecated"; diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 9cc2a047b1..91d2b829ae 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -12,7 +12,6 @@ class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ "graphql.AssertException", - "graphql.Directives", "graphql.ErrorClassification", "graphql.ExceptionWhileDataFetching", "graphql.ExecutionResult", From 012f49cbdea2117ced568d8d3689da263582f11d Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 23 Aug 2025 16:16:44 +0200 Subject: [PATCH 15/82] Annotate ErrorClassification --- src/main/java/graphql/ErrorClassification.java | 3 +++ .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/ErrorClassification.java b/src/main/java/graphql/ErrorClassification.java index db9764f2ce..b40ad0e6d7 100644 --- a/src/main/java/graphql/ErrorClassification.java +++ b/src/main/java/graphql/ErrorClassification.java @@ -1,5 +1,7 @@ package graphql; +import org.jspecify.annotations.NullMarked; + /** * Errors in graphql-java can have a classification to help with the processing * of errors. Custom {@link graphql.GraphQLError} implementations could use @@ -8,6 +10,7 @@ * graphql-java ships with a standard set of error classifications via {@link graphql.ErrorType} */ @PublicApi +@NullMarked public interface ErrorClassification { /** diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 91d2b829ae..1ddb760118 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -12,7 +12,6 @@ class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ "graphql.AssertException", - "graphql.ErrorClassification", "graphql.ExceptionWhileDataFetching", "graphql.ExecutionResult", "graphql.GraphQLContext", From afdd408ebd4341869b814f97784dbd7bf9b1a59d Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 23 Aug 2025 16:18:49 +0200 Subject: [PATCH 16/82] Annotate ExceptionWhileDataFetching --- src/main/java/graphql/ExceptionWhileDataFetching.java | 9 ++++++--- .../graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/graphql/ExceptionWhileDataFetching.java b/src/main/java/graphql/ExceptionWhileDataFetching.java index 73413f55e0..d5d0c61379 100644 --- a/src/main/java/graphql/ExceptionWhileDataFetching.java +++ b/src/main/java/graphql/ExceptionWhileDataFetching.java @@ -3,6 +3,8 @@ import graphql.execution.ResultPath; import graphql.language.SourceLocation; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Collections; import java.util.LinkedHashMap; @@ -16,13 +18,14 @@ * This graphql error will be used if a runtime exception is encountered while a data fetcher is invoked */ @PublicApi +@NullMarked public class ExceptionWhileDataFetching implements GraphQLError { private final String message; private final List path; private final Throwable exception; private final List locations; - private final Map extensions; + private final @Nullable Map extensions; public ExceptionWhileDataFetching(ResultPath path, Throwable exception, SourceLocation sourceLocation) { this.path = assertNotNull(path).toList(); @@ -41,7 +44,7 @@ private String mkMessage(ResultPath path, Throwable exception) { * exception into the ExceptionWhileDataFetching error and hence have custom "extension attributes" * per error message. */ - private Map mkExtensions(Throwable exception) { + private @Nullable Map mkExtensions(Throwable exception) { Map extensions = null; if (exception instanceof GraphQLError) { Map map = ((GraphQLError) exception).getExtensions(); @@ -73,7 +76,7 @@ public List getPath() { } @Override - public Map getExtensions() { + public @Nullable Map getExtensions() { return extensions; } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 1ddb760118..ee2ff62efa 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -12,7 +12,6 @@ class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ "graphql.AssertException", - "graphql.ExceptionWhileDataFetching", "graphql.ExecutionResult", "graphql.GraphQLContext", "graphql.GraphQLError", From 803c9244c6cafee634a1d6fb2c69b4d75bb25816 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Wed, 27 Aug 2025 12:01:12 +0200 Subject: [PATCH 17/82] Annotate ExecutionResult --- src/main/java/graphql/ExecutionResult.java | 18 ++++++++++++------ .../archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/graphql/ExecutionResult.java b/src/main/java/graphql/ExecutionResult.java index f2e94765bb..76d837ee1d 100644 --- a/src/main/java/graphql/ExecutionResult.java +++ b/src/main/java/graphql/ExecutionResult.java @@ -1,6 +1,10 @@ package graphql; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; + import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -9,6 +13,7 @@ * This simple value class represents the result of performing a graphql query. */ @PublicApi +@NullMarked @SuppressWarnings("TypeParameterUnusedInFormals") public interface ExecutionResult { @@ -22,16 +27,16 @@ public interface ExecutionResult { * * @return the data in the result or null if there is none */ - T getData(); + @Nullable T getData(); /** * The graphql specification specifies: - * + *

* "If an error was encountered before execution begins, the data entry should not be present in the result. * If an error was encountered during the execution that prevented a valid response, the data entry in the response should be null." - * + *

* This allows to distinguish between the cases where {@link #getData()} returns null. - * + *

* See : https://graphql.github.io/graphql-spec/June2018/#sec-Data * * @return true if the entry "data" should be present in the result @@ -42,14 +47,14 @@ public interface ExecutionResult { /** * @return a map of extensions or null if there are none */ - Map getExtensions(); + @Nullable Map getExtensions(); /** * The graphql specification says that result of a call should be a map that follows certain rules on what items * should be present. Certain JSON serializers may or may interpret {@link ExecutionResult} to spec, so this method * is provided to produce a map that strictly follows the specification. - * + *

* See : https://spec.graphql.org/October2021/#sec-Response-Format * * @return a map of the result that strictly follows the spec @@ -88,6 +93,7 @@ static Builder newExecutionResult() { return ExecutionResultImpl.newExecutionResult(); } + @NullUnmarked interface Builder> { /** diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index ee2ff62efa..e0b212b457 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -12,7 +12,6 @@ class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ "graphql.AssertException", - "graphql.ExecutionResult", "graphql.GraphQLContext", "graphql.GraphQLError", "graphql.GraphqlErrorBuilder", From a322ed0fec40da2747e60188103c54219db3a832 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Wed, 27 Aug 2025 12:26:43 +0200 Subject: [PATCH 18/82] Annotate GraphQLContext --- src/main/java/graphql/GraphQLContext.java | 18 ++++++++++++------ .../archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/graphql/GraphQLContext.java b/src/main/java/graphql/GraphQLContext.java index 64ec5c406b..7613ec3b96 100644 --- a/src/main/java/graphql/GraphQLContext.java +++ b/src/main/java/graphql/GraphQLContext.java @@ -1,5 +1,9 @@ package graphql; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; + import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -32,12 +36,13 @@ * You can set this up via {@link ExecutionInput#getGraphQLContext()} * * All keys and values in the context MUST be non null. - * + *

* The class is mutable via a thread safe implementation but it is recommended to try to use this class in an immutable way if you can. */ @PublicApi @ThreadSafe @SuppressWarnings("unchecked") +@NullMarked public class GraphQLContext { private final ConcurrentMap map; @@ -66,7 +71,7 @@ public GraphQLContext delete(Object key) { * * @return a value or null */ - public T get(Object key) { + public @Nullable T get(Object key) { return (T) map.get(assertNotNull(key)); } @@ -210,7 +215,7 @@ public GraphQLContext putAll(Consumer contextBuilderCons * * @return the new value associated with the specified key, or null if none */ - public T compute(Object key, BiFunction remappingFunction) { + public @Nullable T compute(Object key, BiFunction remappingFunction) { assertNotNull(remappingFunction); return (T) map.compute(assertNotNull(key), (k, v) -> remappingFunction.apply(k, (T) v)); } @@ -226,7 +231,7 @@ public T compute(Object key, BiFunction rema * @return the current (existing or computed) value associated with the specified key, or null if the computed value is null */ - public T computeIfAbsent(Object key, Function mappingFunction) { + public @Nullable T computeIfAbsent(Object key, Function mappingFunction) { return (T) map.computeIfAbsent(assertNotNull(key), assertNotNull(mappingFunction)); } @@ -241,7 +246,7 @@ public T computeIfAbsent(Object key, Function mappingFu * @return the new value associated with the specified key, or null if none */ - public T computeIfPresent(Object key, BiFunction remappingFunction) { + public @Nullable T computeIfPresent(Object key, BiFunction remappingFunction) { assertNotNull(remappingFunction); return (T) map.computeIfPresent(assertNotNull(key), (k, v) -> remappingFunction.apply(k, (T) v)); } @@ -254,7 +259,7 @@ public Stream> stream() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (this == o) { return true; } @@ -315,6 +320,7 @@ public static Builder newContext() { return new Builder(); } + @NullUnmarked public static class Builder { private final ConcurrentMap map = new ConcurrentHashMap<>(); diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index e0b212b457..e575aaa906 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -12,7 +12,6 @@ class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ "graphql.AssertException", - "graphql.GraphQLContext", "graphql.GraphQLError", "graphql.GraphqlErrorBuilder", "graphql.GraphqlErrorException", From 41320d4f752b93e863b841cb48807248c708ba1d Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 28 Aug 2025 08:49:56 +0200 Subject: [PATCH 19/82] Annotate AssertException and GraphQLError --- src/main/java/graphql/AssertException.java | 3 +++ src/main/java/graphql/GraphQLError.java | 20 +++++++++++-------- .../archunit/JSpecifyAnnotationsCheck.groovy | 2 -- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/graphql/AssertException.java b/src/main/java/graphql/AssertException.java index 92b7fa8f6d..f0e3c54b52 100644 --- a/src/main/java/graphql/AssertException.java +++ b/src/main/java/graphql/AssertException.java @@ -1,7 +1,10 @@ package graphql; +import org.jspecify.annotations.NullMarked; + @PublicApi +@NullMarked public class AssertException extends GraphQLException { public AssertException(String message) { diff --git a/src/main/java/graphql/GraphQLError.java b/src/main/java/graphql/GraphQLError.java index 5f4e0412d0..22c0dc238c 100644 --- a/src/main/java/graphql/GraphQLError.java +++ b/src/main/java/graphql/GraphQLError.java @@ -3,6 +3,8 @@ import graphql.execution.ResultPath; import graphql.language.SourceLocation; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import java.io.Serializable; @@ -20,12 +22,13 @@ * @see GraphQL Spec - 7.1.2 Errors */ @PublicApi +@NullMarked public interface GraphQLError extends Serializable { /** * @return a description of the error intended for the developer as a guide to understand and correct the error */ - String getMessage(); + @Nullable String getMessage(); /** * @return the location(s) within the GraphQL document at which the error occurred. Each {@link SourceLocation} @@ -46,7 +49,7 @@ public interface GraphQLError extends Serializable { * * @return the path in list format */ - default List getPath() { + default @Nullable List getPath() { return null; } @@ -66,7 +69,7 @@ default Map toSpecification() { /** * @return a map of error extensions or null if there are none */ - default Map getExtensions() { + default @Nullable Map getExtensions() { return null; } @@ -91,6 +94,7 @@ static Builder newError() { /** * A builder of {@link GraphQLError}s */ + @NullUnmarked interface Builder> { /** @@ -110,7 +114,7 @@ interface Builder> { * * @return this builder */ - B locations(@Nullable List locations); + B locations(List locations); /** * This adds a location to the error @@ -119,7 +123,7 @@ interface Builder> { * * @return this builder */ - B location(@Nullable SourceLocation location); + B location(SourceLocation location); /** * Sets the path of the message @@ -128,7 +132,7 @@ interface Builder> { * * @return this builder */ - B path(@Nullable ResultPath path); + B path(ResultPath path); /** * Sets the path of the message @@ -137,7 +141,7 @@ interface Builder> { * * @return this builder */ - B path(@Nullable List path); + B path(List path); /** * Sets the {@link ErrorClassification} of the message @@ -155,7 +159,7 @@ interface Builder> { * * @return this builder */ - B extensions(@Nullable Map extensions); + B extensions(Map extensions); /** * @return a newly built GraphqlError diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index e575aaa906..7e1cfeb7b0 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -11,8 +11,6 @@ import spock.lang.Specification class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ - "graphql.AssertException", - "graphql.GraphQLError", "graphql.GraphqlErrorBuilder", "graphql.GraphqlErrorException", "graphql.ParseAndValidate", From 9765c21505724fab51df30673fadbcead3486547 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 28 Aug 2025 08:52:47 +0200 Subject: [PATCH 20/82] Annotate GraphQLErrorBuilder --- src/main/java/graphql/GraphqlErrorBuilder.java | 2 ++ .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/GraphqlErrorBuilder.java b/src/main/java/graphql/GraphqlErrorBuilder.java index eccaadb44d..96d93ad36f 100644 --- a/src/main/java/graphql/GraphqlErrorBuilder.java +++ b/src/main/java/graphql/GraphqlErrorBuilder.java @@ -4,6 +4,7 @@ import graphql.execution.ResultPath; import graphql.language.SourceLocation; import graphql.schema.DataFetchingEnvironment; +import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; import java.util.ArrayList; @@ -21,6 +22,7 @@ */ @SuppressWarnings("unchecked") @PublicApi +@NullUnmarked public class GraphqlErrorBuilder> implements GraphQLError.Builder { private String message; diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 7e1cfeb7b0..0b22207335 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -11,7 +11,6 @@ import spock.lang.Specification class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ - "graphql.GraphqlErrorBuilder", "graphql.GraphqlErrorException", "graphql.ParseAndValidate", "graphql.ParseAndValidateResult", From 0ee713cb80c0fc3a2cb3f5ab47fb88dcaa273f24 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 28 Aug 2025 08:56:45 +0200 Subject: [PATCH 21/82] Annotate GraphQLErrorException --- .../java/graphql/GraphqlErrorException.java | 19 ++++++++++++------- .../archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/java/graphql/GraphqlErrorException.java b/src/main/java/graphql/GraphqlErrorException.java index bfe2cb1d56..428e9436a4 100644 --- a/src/main/java/graphql/GraphqlErrorException.java +++ b/src/main/java/graphql/GraphqlErrorException.java @@ -1,24 +1,28 @@ package graphql; import graphql.language.SourceLocation; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import java.util.Collections; import java.util.List; import java.util.Map; /** - * A base class for graphql runtime exceptions that also implement {@link graphql.GraphQLError} and can be used + * A base class for graphql runtime exceptions that also implement {@link GraphQLError} and can be used * in a general sense direct or have specialisations made of it. *

- * This is aimed amongst other reasons at Kotlin consumers due to https://github.com/graphql-java/graphql-java/issues/1690 + * This is aimed amongst other reasons at Kotlin consumers due to ... * as well as being a way to share common code. */ @PublicApi +@NullMarked public class GraphqlErrorException extends GraphQLException implements GraphQLError { private final List locations; - private final Map extensions; - private final List path; + private final @Nullable Map extensions; + private final @Nullable List path; private final ErrorClassification errorClassification; protected GraphqlErrorException(BuilderBase builder) { @@ -40,12 +44,12 @@ public ErrorClassification getErrorType() { } @Override - public List getPath() { + public @Nullable List getPath() { return path; } @Override - public Map getExtensions() { + public @Nullable Map getExtensions() { return extensions; } @@ -66,6 +70,7 @@ public GraphqlErrorException build() { * @param the derived class * @param the class to be built */ + @NullUnmarked protected abstract static class BuilderBase, B extends GraphqlErrorException> { protected String message; protected Throwable cause; @@ -89,7 +94,7 @@ public T cause(Throwable cause) { return asDerivedType(); } - public T sourceLocation(SourceLocation sourceLocation) { + public T sourceLocation(@Nullable SourceLocation sourceLocation) { return sourceLocations(sourceLocation == null ? null : Collections.singletonList(sourceLocation)); } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 0b22207335..6e5d7ce445 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -11,7 +11,6 @@ import spock.lang.Specification class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ - "graphql.GraphqlErrorException", "graphql.ParseAndValidate", "graphql.ParseAndValidateResult", "graphql.Scalars", From e54fca26f7fddd974652b073e5cde3aac5477b6c Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 28 Aug 2025 09:00:10 +0200 Subject: [PATCH 22/82] Annotate ParseAndValidate --- src/main/java/graphql/ParseAndValidate.java | 18 ++++++++++-------- .../archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/graphql/ParseAndValidate.java b/src/main/java/graphql/ParseAndValidate.java index 0ecb17947d..c1f2b2d45d 100644 --- a/src/main/java/graphql/ParseAndValidate.java +++ b/src/main/java/graphql/ParseAndValidate.java @@ -8,7 +8,7 @@ import graphql.schema.GraphQLSchema; import graphql.validation.ValidationError; import graphql.validation.Validator; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; import java.util.List; import java.util.Locale; @@ -22,11 +22,12 @@ * and the provided schema. */ @PublicApi +@NullMarked public class ParseAndValidate { /** * This {@link GraphQLContext} hint can be used to supply a Predicate to the Validator so that certain rules can be skipped. - * + *

* This is an internal capability that you should use at your own risk. While we intend for this to be present for some time, the validation * rule class names may change, as may this mechanism. */ @@ -42,7 +43,7 @@ public class ParseAndValidate { * * @return a result object that indicates how this operation went */ - public static ParseAndValidateResult parseAndValidate(@NonNull GraphQLSchema graphQLSchema, @NonNull ExecutionInput executionInput) { + public static ParseAndValidateResult parseAndValidate(GraphQLSchema graphQLSchema, ExecutionInput executionInput) { ParseAndValidateResult result = parse(executionInput); if (!result.isFailure()) { List errors = validate(graphQLSchema, result.getDocument(), executionInput.getLocale()); @@ -58,7 +59,7 @@ public static ParseAndValidateResult parseAndValidate(@NonNull GraphQLSchema gra * * @return a result object that indicates how this operation went */ - public static ParseAndValidateResult parse(@NonNull ExecutionInput executionInput) { + public static ParseAndValidateResult parse(ExecutionInput executionInput) { try { // // we allow the caller to specify new parser options by context @@ -66,6 +67,7 @@ public static ParseAndValidateResult parse(@NonNull ExecutionInput executionInpu // we use the query parser options by default if they are not specified parserOptions = ofNullable(parserOptions).orElse(ParserOptions.getDefaultOperationParserOptions()); Parser parser = new Parser(); + // DZ TODO talking point: we can now delete null checks like the line below - you get an IDE warning about it Locale locale = executionInput.getLocale() == null ? Locale.getDefault() : executionInput.getLocale(); ParserEnvironment parserEnvironment = ParserEnvironment.newParserEnvironment() .document(executionInput.getQuery()).parserOptions(parserOptions) @@ -87,7 +89,7 @@ public static ParseAndValidateResult parse(@NonNull ExecutionInput executionInpu * * @return a result object that indicates how this operation went */ - public static List validate(@NonNull GraphQLSchema graphQLSchema, @NonNull Document parsedDocument, @NonNull Locale locale) { + public static List validate(GraphQLSchema graphQLSchema, Document parsedDocument, Locale locale) { return validate(graphQLSchema, parsedDocument, ruleClass -> true, locale); } @@ -99,7 +101,7 @@ public static List validate(@NonNull GraphQLSchema graphQLSchem * * @return a result object that indicates how this operation went */ - public static List validate(@NonNull GraphQLSchema graphQLSchema, @NonNull Document parsedDocument) { + public static List validate(GraphQLSchema graphQLSchema, Document parsedDocument) { return validate(graphQLSchema, parsedDocument, ruleClass -> true, Locale.getDefault()); } @@ -113,7 +115,7 @@ public static List validate(@NonNull GraphQLSchema graphQLSchem * * @return a result object that indicates how this operation went */ - public static List validate(@NonNull GraphQLSchema graphQLSchema, @NonNull Document parsedDocument, @NonNull Predicate> rulePredicate, @NonNull Locale locale) { + public static List validate(GraphQLSchema graphQLSchema, Document parsedDocument, Predicate> rulePredicate, Locale locale) { Validator validator = new Validator(); return validator.validateDocument(graphQLSchema, parsedDocument, rulePredicate, locale); } @@ -127,7 +129,7 @@ public static List validate(@NonNull GraphQLSchema graphQLSchem * * @return a result object that indicates how this operation went */ - public static List validate(@NonNull GraphQLSchema graphQLSchema, @NonNull Document parsedDocument, @NonNull Predicate> rulePredicate) { + public static List validate(GraphQLSchema graphQLSchema, Document parsedDocument, Predicate> rulePredicate) { Validator validator = new Validator(); return validator.validateDocument(graphQLSchema, parsedDocument, rulePredicate, Locale.getDefault()); } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 6e5d7ce445..4652542337 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -11,7 +11,6 @@ import spock.lang.Specification class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ - "graphql.ParseAndValidate", "graphql.ParseAndValidateResult", "graphql.Scalars", "graphql.SerializationError", From 603c00d65a1bb1936f2db6891e478e42159bd4bc Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 28 Aug 2025 09:03:30 +0200 Subject: [PATCH 23/82] Annotate ParseAndValidateResult --- src/main/java/graphql/ParseAndValidateResult.java | 13 ++++++++----- .../archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/graphql/ParseAndValidateResult.java b/src/main/java/graphql/ParseAndValidateResult.java index d7f1332e90..ea143364c4 100644 --- a/src/main/java/graphql/ParseAndValidateResult.java +++ b/src/main/java/graphql/ParseAndValidateResult.java @@ -5,6 +5,8 @@ import graphql.language.Document; import graphql.parser.InvalidSyntaxException; import graphql.validation.ValidationError; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -18,9 +20,9 @@ @PublicApi public class ParseAndValidateResult { - private final Document document; + private final @Nullable Document document; private final Map variables; - private final InvalidSyntaxException syntaxException; + private final @Nullable InvalidSyntaxException syntaxException; private final List validationErrors; private ParseAndValidateResult(Builder builder) { @@ -40,7 +42,7 @@ public boolean isFailure() { /** * @return the parsed document or null if it's syntactically invalid. */ - public Document getDocument() { + public @Nullable Document getDocument() { return document; } @@ -54,7 +56,7 @@ public Map getVariables() { /** * @return the parsed document and variables or null if it's syntactically invalid. */ - public DocumentAndVariables getDocumentAndVariables() { + public @Nullable DocumentAndVariables getDocumentAndVariables() { if (document != null) { return DocumentAndVariables.newDocumentAndVariables().document(document).variables(variables).build(); } @@ -64,7 +66,7 @@ public DocumentAndVariables getDocumentAndVariables() { /** * @return the syntax exception or null if it's syntactically valid. */ - public InvalidSyntaxException getSyntaxException() { + public @Nullable InvalidSyntaxException getSyntaxException() { return syntaxException; } @@ -100,6 +102,7 @@ public static Builder newResult() { return new Builder(); } + @NullUnmarked public static class Builder { private Document document; private Map variables = ImmutableKit.emptyMap(); diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 4652542337..e7f2f30765 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -11,7 +11,6 @@ import spock.lang.Specification class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ - "graphql.ParseAndValidateResult", "graphql.Scalars", "graphql.SerializationError", "graphql.TypeMismatchError", From 9fcaea108a90c5d39e602a0ae31de39f64230796 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 28 Aug 2025 09:05:02 +0200 Subject: [PATCH 24/82] Annotate Scalars --- src/main/java/graphql/Scalars.java | 12 +++++++----- .../graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/graphql/Scalars.java b/src/main/java/graphql/Scalars.java index 25f9dc640c..72fde33574 100644 --- a/src/main/java/graphql/Scalars.java +++ b/src/main/java/graphql/Scalars.java @@ -7,6 +7,7 @@ import graphql.scalar.GraphqlIntCoercing; import graphql.scalar.GraphqlStringCoercing; import graphql.schema.GraphQLScalarType; +import org.jspecify.annotations.NullMarked; /** * This contains the implementations of the Scalar types that ship with graphql-java. Some are proscribed @@ -17,10 +18,11 @@ * more specifically https://spec.graphql.org/draft/#sec-Scalars */ @PublicApi +@NullMarked public class Scalars { /** - * This represents the "Int" type as defined in the graphql specification : https://spec.graphql.org/October2021/#sec-Int + * This represents the "Int" type as defined in the graphql specification : ... *

* The Int scalar type represents a signed 32‐bit numeric non‐fractional value. */ @@ -28,7 +30,7 @@ public class Scalars { .name("Int").description("Built-in Int").coercing(new GraphqlIntCoercing()).build(); /** - * This represents the "Float" type as defined in the graphql specification : https://spec.graphql.org/October2021/#sec-Float + * This represents the "Float" type as defined in the graphql specification : ... *

* Note: The Float type in GraphQL is equivalent to Double in Java. (double precision IEEE 754) */ @@ -36,19 +38,19 @@ public class Scalars { .name("Float").description("Built-in Float").coercing(new GraphqlFloatCoercing()).build(); /** - * This represents the "String" type as defined in the graphql specification : https://spec.graphql.org/October2021/#sec-String + * This represents the "String" type as defined in the graphql specification : ... */ public static final GraphQLScalarType GraphQLString = GraphQLScalarType.newScalar() .name("String").description("Built-in String").coercing(new GraphqlStringCoercing()).build(); /** - * This represents the "Boolean" type as defined in the graphql specification : https://spec.graphql.org/October2021/#sec-Boolean + * This represents the "Boolean" type as defined in the graphql specification : ... */ public static final GraphQLScalarType GraphQLBoolean = GraphQLScalarType.newScalar() .name("Boolean").description("Built-in Boolean").coercing(new GraphqlBooleanCoercing()).build(); /** - * This represents the "ID" type as defined in the graphql specification : https://spec.graphql.org/October2021/#sec-ID + * This represents the "ID" type as defined in the graphql specification : ... *

* The ID scalar type represents a unique identifier, often used to re-fetch an object or as the key for a cache. The * ID type is serialized in the same way as a String; however, it is not intended to be human‐readable. While it is diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index e7f2f30765..d6e66a9778 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -11,7 +11,6 @@ import spock.lang.Specification class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ - "graphql.Scalars", "graphql.SerializationError", "graphql.TypeMismatchError", "graphql.TypeResolutionEnvironment", From 88b02f3da309014c674725d897a5bb639918e10b Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 28 Aug 2025 09:06:52 +0200 Subject: [PATCH 25/82] Annotate SerializationError --- src/main/java/graphql/SerializationError.java | 5 ++++- .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/SerializationError.java b/src/main/java/graphql/SerializationError.java index 9e03017565..193bc0a182 100644 --- a/src/main/java/graphql/SerializationError.java +++ b/src/main/java/graphql/SerializationError.java @@ -4,6 +4,8 @@ import graphql.execution.ResultPath; import graphql.language.SourceLocation; import graphql.schema.CoercingSerializeException; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.List; import java.util.Map; @@ -12,6 +14,7 @@ import static java.lang.String.format; @PublicApi +@NullMarked public class SerializationError implements GraphQLError { private final String message; @@ -53,7 +56,7 @@ public List getPath() { } @Override - public Map getExtensions() { + public @Nullable Map getExtensions() { return exception.getExtensions(); } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index d6e66a9778..361f84a442 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -11,7 +11,6 @@ import spock.lang.Specification class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ - "graphql.SerializationError", "graphql.TypeMismatchError", "graphql.TypeResolutionEnvironment", "graphql.UnresolvedTypeError", From e65217cca712e0dea340fb6512162cdac352e6ca Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 28 Aug 2025 09:14:30 +0200 Subject: [PATCH 26/82] Update GraphQLError with a nullable location list --- src/main/java/graphql/GraphQLError.java | 2 +- src/main/java/graphql/GraphqlErrorException.java | 4 ++-- src/main/java/graphql/SerializationError.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/graphql/GraphQLError.java b/src/main/java/graphql/GraphQLError.java index 22c0dc238c..948e798284 100644 --- a/src/main/java/graphql/GraphQLError.java +++ b/src/main/java/graphql/GraphQLError.java @@ -34,7 +34,7 @@ public interface GraphQLError extends Serializable { * @return the location(s) within the GraphQL document at which the error occurred. Each {@link SourceLocation} * describes the beginning of an associated syntax element */ - List getLocations(); + @Nullable List getLocations(); /** * @return an object classifying this error diff --git a/src/main/java/graphql/GraphqlErrorException.java b/src/main/java/graphql/GraphqlErrorException.java index 428e9436a4..dd84674d19 100644 --- a/src/main/java/graphql/GraphqlErrorException.java +++ b/src/main/java/graphql/GraphqlErrorException.java @@ -20,7 +20,7 @@ @NullMarked public class GraphqlErrorException extends GraphQLException implements GraphQLError { - private final List locations; + private final @Nullable List locations; private final @Nullable Map extensions; private final @Nullable List path; private final ErrorClassification errorClassification; @@ -34,7 +34,7 @@ protected GraphqlErrorException(BuilderBase builder) { } @Override - public List getLocations() { + public @Nullable List getLocations() { return locations; } diff --git a/src/main/java/graphql/SerializationError.java b/src/main/java/graphql/SerializationError.java index 193bc0a182..2f4e1cf4c0 100644 --- a/src/main/java/graphql/SerializationError.java +++ b/src/main/java/graphql/SerializationError.java @@ -41,7 +41,7 @@ public String getMessage() { } @Override - public List getLocations() { + public @Nullable List getLocations() { return exception.getLocations(); } From 19000a57e09f090a660d765308ee83498ba2c06a Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 28 Aug 2025 09:16:11 +0200 Subject: [PATCH 27/82] Annotate TypeMismatchError --- src/main/java/graphql/TypeMismatchError.java | 5 ++++- .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/TypeMismatchError.java b/src/main/java/graphql/TypeMismatchError.java index f5e41b3391..3b59e5808a 100644 --- a/src/main/java/graphql/TypeMismatchError.java +++ b/src/main/java/graphql/TypeMismatchError.java @@ -12,6 +12,8 @@ import graphql.schema.GraphQLScalarType; import graphql.schema.GraphQLType; import graphql.schema.GraphQLUnionType; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.LinkedHashMap; import java.util.List; @@ -21,6 +23,7 @@ import static java.lang.String.format; @PublicApi +@NullMarked public class TypeMismatchError implements GraphQLError { private final String message; @@ -65,7 +68,7 @@ public String getMessage() { } @Override - public List getLocations() { + public @Nullable List getLocations() { return null; } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 361f84a442..4fe456eb27 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -11,7 +11,6 @@ import spock.lang.Specification class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ - "graphql.TypeMismatchError", "graphql.TypeResolutionEnvironment", "graphql.UnresolvedTypeError", "graphql.agent.result.ExecutionTrackingResult", From 313bf4f69f037565c8c95b941f509b2be20ec3d4 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 28 Aug 2025 09:17:26 +0200 Subject: [PATCH 28/82] Annotate UnresolvedTypeError --- src/main/java/graphql/UnresolvedTypeError.java | 5 ++++- .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/UnresolvedTypeError.java b/src/main/java/graphql/UnresolvedTypeError.java index 8f38b1fc4e..ba9bf333a5 100644 --- a/src/main/java/graphql/UnresolvedTypeError.java +++ b/src/main/java/graphql/UnresolvedTypeError.java @@ -4,6 +4,8 @@ import graphql.execution.ResultPath; import graphql.execution.UnresolvedTypeException; import graphql.language.SourceLocation; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.List; @@ -12,6 +14,7 @@ import static java.lang.String.format; @PublicApi +@NullMarked public class UnresolvedTypeError implements GraphQLError { private final String message; @@ -45,7 +48,7 @@ public String getMessage() { } @Override - public List getLocations() { + public @Nullable List getLocations() { return null; } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 4fe456eb27..3fb127f481 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -12,7 +12,6 @@ class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ "graphql.TypeResolutionEnvironment", - "graphql.UnresolvedTypeError", "graphql.agent.result.ExecutionTrackingResult", "graphql.analysis.FieldComplexityCalculator", "graphql.analysis.FieldComplexityEnvironment", From 0846c55150744483d9f6a2bf0df4a2fc9eb452c3 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 14:09:18 +0200 Subject: [PATCH 29/82] Remove agent as no longer exists --- src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 3fb127f481..b1ce2b731f 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -12,7 +12,6 @@ class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ "graphql.TypeResolutionEnvironment", - "graphql.agent.result.ExecutionTrackingResult", "graphql.analysis.FieldComplexityCalculator", "graphql.analysis.FieldComplexityEnvironment", "graphql.analysis.MaxQueryComplexityInstrumentation", From d2d87cd093967734a18d16b3cbe98f7a37f1c25d Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 14:31:43 +0200 Subject: [PATCH 30/82] Add JSpecify prompt --- .claude/commands/jspecify-annotate.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .claude/commands/jspecify-annotate.md diff --git a/.claude/commands/jspecify-annotate.md b/.claude/commands/jspecify-annotate.md new file mode 100644 index 0000000000..b0bf54986f --- /dev/null +++ b/.claude/commands/jspecify-annotate.md @@ -0,0 +1,15 @@ +I have already asked IntelliJ to infer nullity on this class. Can you help me make this more accurate. + +Note that JSpecify is already used in this repository so it's already imported. + +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 +3. Check Javadoc @param tags mentioning "null", "nullable", "may be null" +4. Check Javadoc @return tags mentioning "null", "optional", "if available" +5. GraphQL specification semantics (nullable fields, non-null by default) +6. Method implementations that return null or check for null + +IntelliJ's infer nullity code analysis isn't comprehensive so feel free to make corrections. + +Finally, please check all of this works, by running the NullAway compile check. \ No newline at end of file From f4b05bb2762a1d4a604c47d49a23eca3e24f4998 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 14:50:14 +0200 Subject: [PATCH 31/82] Fix NullAway issues with assertions --- src/main/java/graphql/GraphQL.java | 4 ++-- src/main/java/graphql/ParseAndValidate.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/GraphQL.java b/src/main/java/graphql/GraphQL.java index 16d14ab4b9..7638cb8b08 100644 --- a/src/main/java/graphql/GraphQL.java +++ b/src/main/java/graphql/GraphQL.java @@ -559,14 +559,14 @@ private PreparsedDocumentEntry parseAndValidate(AtomicReference ParseAndValidateResult parseResult = parse(executionInput, graphQLSchema, instrumentationState); if (parseResult.isFailure()) { - return new PreparsedDocumentEntry(parseResult.getSyntaxException().toInvalidSyntaxError()); + return new PreparsedDocumentEntry(assertNotNull(parseResult.getSyntaxException(), () -> "Parse result syntax exception cannot be null when failed").toInvalidSyntaxError()); } else { final Document document = parseResult.getDocument(); // they may have changed the document and the variables via instrumentation so update the reference to it executionInput = executionInput.transform(builder -> builder.variables(parseResult.getVariables())); executionInputRef.set(executionInput); - final List errors = validate(executionInput, document, graphQLSchema, instrumentationState); + final List errors = validate(executionInput, assertNotNull(document, () -> "Document cannot be null when parse succeeded"), graphQLSchema, instrumentationState); if (!errors.isEmpty()) { return new PreparsedDocumentEntry(document, errors); } diff --git a/src/main/java/graphql/ParseAndValidate.java b/src/main/java/graphql/ParseAndValidate.java index c1f2b2d45d..df43ef87c0 100644 --- a/src/main/java/graphql/ParseAndValidate.java +++ b/src/main/java/graphql/ParseAndValidate.java @@ -14,6 +14,7 @@ import java.util.Locale; import java.util.function.Predicate; +import static graphql.Assert.assertNotNull; import static java.util.Optional.ofNullable; /** @@ -46,7 +47,7 @@ public class ParseAndValidate { public static ParseAndValidateResult parseAndValidate(GraphQLSchema graphQLSchema, ExecutionInput executionInput) { ParseAndValidateResult result = parse(executionInput); if (!result.isFailure()) { - List errors = validate(graphQLSchema, result.getDocument(), executionInput.getLocale()); + List errors = validate(graphQLSchema, assertNotNull(result.getDocument(), () -> "Parse result document cannot be null when parse succeeded"), executionInput.getLocale()); return result.transform(builder -> builder.validationErrors(errors)); } return result; From ab1ce8d20fafce24ef52f435c7dc83dc0dbdda7e Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 14:50:55 +0200 Subject: [PATCH 32/82] Remove null checks --- src/main/java/graphql/GraphQL.java | 2 +- src/main/java/graphql/ParseAndValidate.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/GraphQL.java b/src/main/java/graphql/GraphQL.java index 7638cb8b08..e191f1eccc 100644 --- a/src/main/java/graphql/GraphQL.java +++ b/src/main/java/graphql/GraphQL.java @@ -599,7 +599,7 @@ private List validate(ExecutionInput executionInput, Document d validationCtx.onDispatched(); Predicate> validationRulePredicate = executionInput.getGraphQLContext().getOrDefault(ParseAndValidate.INTERNAL_VALIDATION_PREDICATE_HINT, r -> true); - Locale locale = executionInput.getLocale() != null ? executionInput.getLocale() : Locale.getDefault(); + Locale locale = executionInput.getLocale(); List validationErrors = ParseAndValidate.validate(graphQLSchema, document, validationRulePredicate, locale); validationCtx.onCompleted(validationErrors, null); diff --git a/src/main/java/graphql/ParseAndValidate.java b/src/main/java/graphql/ParseAndValidate.java index df43ef87c0..9d23030977 100644 --- a/src/main/java/graphql/ParseAndValidate.java +++ b/src/main/java/graphql/ParseAndValidate.java @@ -68,8 +68,7 @@ public static ParseAndValidateResult parse(ExecutionInput executionInput) { // we use the query parser options by default if they are not specified parserOptions = ofNullable(parserOptions).orElse(ParserOptions.getDefaultOperationParserOptions()); Parser parser = new Parser(); - // DZ TODO talking point: we can now delete null checks like the line below - you get an IDE warning about it - Locale locale = executionInput.getLocale() == null ? Locale.getDefault() : executionInput.getLocale(); + Locale locale = executionInput.getLocale(); ParserEnvironment parserEnvironment = ParserEnvironment.newParserEnvironment() .document(executionInput.getQuery()).parserOptions(parserOptions) .locale(locale) From 241b7a0cc3729e9ca397bb0f47dd70438d680ad9 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 14:55:47 +0200 Subject: [PATCH 33/82] Annotate DefaultConnection --- .claude/commands/jspecify-annotate.md | 6 +++++- src/main/java/graphql/relay/DefaultConnection.java | 5 ++++- .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.claude/commands/jspecify-annotate.md b/.claude/commands/jspecify-annotate.md index b0bf54986f..aaa84e645a 100644 --- a/.claude/commands/jspecify-annotate.md +++ b/.claude/commands/jspecify-annotate.md @@ -12,4 +12,8 @@ Analyze this Java class and add JSpecify annotations based on: IntelliJ's infer nullity code analysis isn't comprehensive so feel free to make corrections. -Finally, please check all of this works, by running the NullAway compile check. \ No newline at end of file +Finally, please check all of 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. + +Finally, can you remove this class from the JSpecifyAnnotationsCheck as an exemption. Thanks \ No newline at end of file diff --git a/src/main/java/graphql/relay/DefaultConnection.java b/src/main/java/graphql/relay/DefaultConnection.java index e6db4dc4ea..6600941734 100644 --- a/src/main/java/graphql/relay/DefaultConnection.java +++ b/src/main/java/graphql/relay/DefaultConnection.java @@ -2,6 +2,8 @@ import com.google.common.collect.ImmutableList; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Collections; import java.util.List; @@ -13,6 +15,7 @@ * A default implementation of {@link graphql.relay.Connection} */ @PublicApi +@NullMarked public class DefaultConnection implements Connection { private final ImmutableList> edges; @@ -42,7 +45,7 @@ public PageInfo getPageInfo() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (this == o) { return true; } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index b1ce2b731f..a65119e171 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -192,7 +192,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.parser.ParserOptions", "graphql.relay.Connection", "graphql.relay.ConnectionCursor", - "graphql.relay.DefaultConnection", "graphql.relay.DefaultConnectionCursor", "graphql.relay.DefaultEdge", "graphql.relay.DefaultPageInfo", From 7ffd1ba293e5d76336ba169fa777cbbd7552a221 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:10:08 +0200 Subject: [PATCH 34/82] Annotate Relay Connection --- src/main/java/graphql/relay/Connection.java | 11 +++++++---- .../graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/graphql/relay/Connection.java b/src/main/java/graphql/relay/Connection.java index aec2158f70..97106aee77 100644 --- a/src/main/java/graphql/relay/Connection.java +++ b/src/main/java/graphql/relay/Connection.java @@ -1,25 +1,28 @@ package graphql.relay; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.List; /** * This represents a connection in Relay, which is a list of {@link graphql.relay.Edge edge}s * as well as a {@link graphql.relay.PageInfo pageInfo} that describes the pagination of that list. - * + *

* See https://facebook.github.io/relay/graphql/connections.htm */ @PublicApi +@NullMarked public interface Connection { /** - * @return a list of {@link graphql.relay.Edge}s that are really a node of data and its cursor + * @return a list of {@link graphql.relay.Edge}s that contain a node of data and its cursor. Can be null as defined in the spec. */ - List> getEdges(); + @Nullable List> getEdges(); /** - * @return {@link graphql.relay.PageInfo} pagination data about that list of edges + * @return {@link graphql.relay.PageInfo} pagination data about that list of edges. Not nullable by definition in the spec. */ PageInfo getPageInfo(); diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index a65119e171..532a4f346d 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -190,7 +190,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.parser.Parser", "graphql.parser.ParserEnvironment", "graphql.parser.ParserOptions", - "graphql.relay.Connection", "graphql.relay.ConnectionCursor", "graphql.relay.DefaultConnectionCursor", "graphql.relay.DefaultEdge", From 003958fa2e008ddff7c49762c2714ecd37890a5f Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:12:36 +0200 Subject: [PATCH 35/82] Update URL --- src/main/java/graphql/relay/Connection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/graphql/relay/Connection.java b/src/main/java/graphql/relay/Connection.java index 97106aee77..a0ebf5d1dc 100644 --- a/src/main/java/graphql/relay/Connection.java +++ b/src/main/java/graphql/relay/Connection.java @@ -10,7 +10,7 @@ * This represents a connection in Relay, which is a list of {@link graphql.relay.Edge edge}s * as well as a {@link graphql.relay.PageInfo pageInfo} that describes the pagination of that list. *

- * See https://facebook.github.io/relay/graphql/connections.htm + * See https://relay.dev/graphql/connections.htm */ @PublicApi @NullMarked From ba3de3e997017e70bdb1b3dd2987bd1db3347c9c Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:19:39 +0200 Subject: [PATCH 36/82] Annotate DefaultConnectionCursor --- src/main/java/graphql/relay/DefaultConnectionCursor.java | 7 +++++-- .../graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/relay/DefaultConnectionCursor.java b/src/main/java/graphql/relay/DefaultConnectionCursor.java index 0360f8c727..a478983362 100644 --- a/src/main/java/graphql/relay/DefaultConnectionCursor.java +++ b/src/main/java/graphql/relay/DefaultConnectionCursor.java @@ -2,16 +2,19 @@ import graphql.Assert; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Objects; @PublicApi +@NullMarked public class DefaultConnectionCursor implements ConnectionCursor { private final String value; public DefaultConnectionCursor(String value) { - Assert.assertTrue(value != null && !value.isEmpty(), () -> "connection value cannot be null or empty"); + Assert.assertTrue(!value.isEmpty(), () -> "connection value cannot be null or empty"); this.value = value; } @@ -21,7 +24,7 @@ public String getValue() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (this == o) { return true; } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 532a4f346d..4372e49640 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -191,7 +191,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.parser.ParserEnvironment", "graphql.parser.ParserOptions", "graphql.relay.ConnectionCursor", - "graphql.relay.DefaultConnectionCursor", "graphql.relay.DefaultEdge", "graphql.relay.DefaultPageInfo", "graphql.relay.Edge", From 8de5a7559d4a0a9adb967192e37e8c609eb5f67f Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:21:22 +0200 Subject: [PATCH 37/82] Annotate ConnectionCursor --- src/main/java/graphql/relay/ConnectionCursor.java | 6 ++++-- .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/relay/ConnectionCursor.java b/src/main/java/graphql/relay/ConnectionCursor.java index 29a4f7e9fa..3eac9771b3 100644 --- a/src/main/java/graphql/relay/ConnectionCursor.java +++ b/src/main/java/graphql/relay/ConnectionCursor.java @@ -1,16 +1,18 @@ package graphql.relay; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; /** * Represents a {@link Connection connection} cursor in Relay which is an opaque * string that the server understands. Often this is base64 encoded but the spec only * mandates that it be an opaque cursor so meaning can't be inferred from it (to prevent cheating like - * pre calculating the next cursor on the client say) - * + * pre-calculating the next cursor on the client say) + *

* See https://facebook.github.io/relay/graphql/connections.htm#sec-Cursor */ @PublicApi +@NullMarked public interface ConnectionCursor { /** diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 4372e49640..91ad013adc 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -190,7 +190,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.parser.Parser", "graphql.parser.ParserEnvironment", "graphql.parser.ParserOptions", - "graphql.relay.ConnectionCursor", "graphql.relay.DefaultEdge", "graphql.relay.DefaultPageInfo", "graphql.relay.Edge", From 0bee1f8741bb419f0fd52c3db940289a32e20c91 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:22:52 +0200 Subject: [PATCH 38/82] Annotate DefaultEdge --- src/main/java/graphql/relay/DefaultEdge.java | 12 +++++++----- .../graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/graphql/relay/DefaultEdge.java b/src/main/java/graphql/relay/DefaultEdge.java index e43f4c54ce..0923b7ac83 100644 --- a/src/main/java/graphql/relay/DefaultEdge.java +++ b/src/main/java/graphql/relay/DefaultEdge.java @@ -1,25 +1,27 @@ package graphql.relay; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Objects; import static graphql.Assert.assertNotNull; @PublicApi +@NullMarked public class DefaultEdge implements Edge { - private final T node; + private final @Nullable T node; private final ConnectionCursor cursor; - public DefaultEdge(T node, ConnectionCursor cursor) { + public DefaultEdge(@Nullable T node, ConnectionCursor cursor) { this.cursor = assertNotNull(cursor, () -> "cursor cannot be null"); this.node = node; } - @Override - public T getNode() { + public @Nullable T getNode() { return node; } @@ -29,7 +31,7 @@ public ConnectionCursor getCursor() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (this == o) { return true; } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 91ad013adc..c759399d16 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -190,7 +190,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.parser.Parser", "graphql.parser.ParserEnvironment", "graphql.parser.ParserOptions", - "graphql.relay.DefaultEdge", "graphql.relay.DefaultPageInfo", "graphql.relay.Edge", "graphql.relay.PageInfo", From a6e7382392507e9e48db531c8af487a35b6905d4 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:28:00 +0200 Subject: [PATCH 39/82] Annotate DefaultPageInfo --- src/main/java/graphql/relay/DefaultPageInfo.java | 16 +++++++++------- .../archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/graphql/relay/DefaultPageInfo.java b/src/main/java/graphql/relay/DefaultPageInfo.java index d76ef04fb1..2e9f8d00ab 100644 --- a/src/main/java/graphql/relay/DefaultPageInfo.java +++ b/src/main/java/graphql/relay/DefaultPageInfo.java @@ -2,18 +2,21 @@ import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Objects; @PublicApi +@NullMarked public class DefaultPageInfo implements PageInfo { - private final ConnectionCursor startCursor; - private final ConnectionCursor endCursor; + private final @Nullable ConnectionCursor startCursor; + private final @Nullable ConnectionCursor endCursor; private final boolean hasPreviousPage; private final boolean hasNextPage; - public DefaultPageInfo(ConnectionCursor startCursor, ConnectionCursor endCursor, boolean hasPreviousPage, boolean hasNextPage) { + public DefaultPageInfo(@Nullable ConnectionCursor startCursor, @Nullable ConnectionCursor endCursor, boolean hasPreviousPage, boolean hasNextPage) { this.startCursor = startCursor; this.endCursor = endCursor; this.hasPreviousPage = hasPreviousPage; @@ -21,13 +24,12 @@ public DefaultPageInfo(ConnectionCursor startCursor, ConnectionCursor endCursor, } @Override - public ConnectionCursor getStartCursor() { + public @Nullable ConnectionCursor getStartCursor() { return startCursor; } - @Override - public ConnectionCursor getEndCursor() { + public @Nullable ConnectionCursor getEndCursor() { return endCursor; } @@ -42,7 +44,7 @@ public boolean isHasNextPage() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (this == o) { return true; } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index c759399d16..dd3b99265c 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -190,7 +190,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.parser.Parser", "graphql.parser.ParserEnvironment", "graphql.parser.ParserOptions", - "graphql.relay.DefaultPageInfo", "graphql.relay.Edge", "graphql.relay.PageInfo", "graphql.relay.Relay", From e5084532c1cc4bfb0103aa372d6d8071007d1665 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:29:44 +0200 Subject: [PATCH 40/82] Annotate Edge --- src/main/java/graphql/relay/Edge.java | 9 ++++++--- .../graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/graphql/relay/Edge.java b/src/main/java/graphql/relay/Edge.java index 18725d9934..1b50311ea2 100644 --- a/src/main/java/graphql/relay/Edge.java +++ b/src/main/java/graphql/relay/Edge.java @@ -1,19 +1,22 @@ package graphql.relay; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Represents an edge in Relay which is essentially a node of data T and the cursor for that node. - * + *

* See https://facebook.github.io/relay/graphql/connections.htm#sec-Edge-Types */ @PublicApi +@NullMarked public interface Edge { /** - * @return the node of data that this edge represents + * @return the node of data that this edge represents, or null if the node failed to resolve */ - T getNode(); + @Nullable T getNode(); /** * @return the cursor for this edge node diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index dd3b99265c..75a819e282 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -190,7 +190,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.parser.Parser", "graphql.parser.ParserEnvironment", "graphql.parser.ParserOptions", - "graphql.relay.Edge", "graphql.relay.PageInfo", "graphql.relay.Relay", "graphql.relay.SimpleListConnection", From ee765746dca0454dbd6e64888a9e895274b2e7fe Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:32:00 +0200 Subject: [PATCH 41/82] Annotate PageInfo --- src/main/java/graphql/relay/PageInfo.java | 7 +++++-- .../graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/relay/PageInfo.java b/src/main/java/graphql/relay/PageInfo.java index 73c2dbe10c..6d9b59c551 100644 --- a/src/main/java/graphql/relay/PageInfo.java +++ b/src/main/java/graphql/relay/PageInfo.java @@ -1,6 +1,8 @@ package graphql.relay; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * Represents pagination information in Relay about {@link graphql.relay.Edge edges} when used @@ -9,17 +11,18 @@ * See https://facebook.github.io/relay/graphql/connections.htm#sec-undefined.PageInfo */ @PublicApi +@NullMarked public interface PageInfo { /** * @return cursor to the first edge, or null if this page is empty. */ - ConnectionCursor getStartCursor(); + @Nullable ConnectionCursor getStartCursor(); /** * @return cursor to the last edge, or null if this page is empty. */ - ConnectionCursor getEndCursor(); + @Nullable ConnectionCursor getEndCursor(); /** * @return true if and only if this page is not the first page. only meaningful when you gave the {@code last} argument. diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 75a819e282..afb604b906 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -190,7 +190,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.parser.Parser", "graphql.parser.ParserEnvironment", "graphql.parser.ParserOptions", - "graphql.relay.PageInfo", "graphql.relay.Relay", "graphql.relay.SimpleListConnection", "graphql.schema.AsyncDataFetcher", From b7a7be69b2827fd6148abdf5c6ec88c6c2184ac4 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:40:29 +0200 Subject: [PATCH 42/82] Annotate SimpleListConnection --- .../java/graphql/relay/SimpleListConnection.java | 12 +++++++----- .../graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/graphql/relay/SimpleListConnection.java b/src/main/java/graphql/relay/SimpleListConnection.java index 8648080f1c..4ddf89a3e9 100644 --- a/src/main/java/graphql/relay/SimpleListConnection.java +++ b/src/main/java/graphql/relay/SimpleListConnection.java @@ -3,8 +3,9 @@ import graphql.PublicApi; import graphql.TrivialDataFetcher; import graphql.collect.ImmutableKit; -import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -17,7 +18,8 @@ import static java.util.Base64.getEncoder; @PublicApi -public class SimpleListConnection implements DataFetcher>, TrivialDataFetcher> { +@NullMarked +public class SimpleListConnection implements TrivialDataFetcher> { static final String DUMMY_CURSOR_PREFIX = "simple-cursor"; private final String prefix; @@ -25,7 +27,7 @@ public class SimpleListConnection implements DataFetcher>, Triv public SimpleListConnection(List data, String prefix) { this.data = assertNotNull(data, () -> " data cannot be null"); - assertTrue(prefix != null && !prefix.isEmpty(), () -> "prefix cannot be null or empty"); + assertTrue(!prefix.isEmpty(), () -> "prefix cannot be null or empty"); this.prefix = prefix; } @@ -116,7 +118,7 @@ private Connection emptyConnection() { * * @return a connection cursor */ - public ConnectionCursor cursorForObjectInConnection(T object) { + public @Nullable ConnectionCursor cursorForObjectInConnection(T object) { int index = data.indexOf(object); if (index == -1) { return null; @@ -125,7 +127,7 @@ public ConnectionCursor cursorForObjectInConnection(T object) { return new DefaultConnectionCursor(cursor); } - private int getOffsetFromCursor(String cursor, int defaultValue) { + private int getOffsetFromCursor(@Nullable String cursor, int defaultValue) { if (cursor == null) { return defaultValue; } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index afb604b906..4cc34c7810 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -191,7 +191,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.parser.ParserEnvironment", "graphql.parser.ParserOptions", "graphql.relay.Relay", - "graphql.relay.SimpleListConnection", "graphql.schema.AsyncDataFetcher", "graphql.schema.CoercingParseLiteralException", "graphql.schema.CoercingParseValueException", From 81beb0db53a979fc9250f4183b65a3958c5d5909 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:46:08 +0200 Subject: [PATCH 43/82] Annotate Relay --- src/main/java/graphql/relay/Relay.java | 6 ++++-- .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/relay/Relay.java b/src/main/java/graphql/relay/Relay.java index b2ffe26fb9..33db8bee8e 100644 --- a/src/main/java/graphql/relay/Relay.java +++ b/src/main/java/graphql/relay/Relay.java @@ -10,6 +10,7 @@ import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; import graphql.schema.TypeResolver; +import org.jspecify.annotations.NullMarked; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -32,10 +33,11 @@ /** * This can be used to compose graphql runtime types that implement * that Relay specification. - * - * See https://facebook.github.io/relay/graphql/connections.htm + *

+ * See https://relay.dev/graphql/connections.htm */ @PublicApi +@NullMarked public class Relay { public static final String NODE = "Node"; diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 4cc34c7810..fcfc1da46c 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -190,7 +190,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.parser.Parser", "graphql.parser.ParserEnvironment", "graphql.parser.ParserOptions", - "graphql.relay.Relay", "graphql.schema.AsyncDataFetcher", "graphql.schema.CoercingParseLiteralException", "graphql.schema.CoercingParseValueException", From 9c5e4c8b184781b120142b9341af45c6f7352dd2 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 17:28:53 +0200 Subject: [PATCH 44/82] Annotate ValidationErrorClassification and ValidationErrorType --- .../java/graphql/validation/ValidationErrorClassification.java | 2 ++ src/main/java/graphql/validation/ValidationErrorType.java | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/validation/ValidationErrorClassification.java b/src/main/java/graphql/validation/ValidationErrorClassification.java index 2a9ad78722..60d78300d6 100644 --- a/src/main/java/graphql/validation/ValidationErrorClassification.java +++ b/src/main/java/graphql/validation/ValidationErrorClassification.java @@ -2,7 +2,9 @@ import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; @PublicApi +@NullMarked public interface ValidationErrorClassification { } diff --git a/src/main/java/graphql/validation/ValidationErrorType.java b/src/main/java/graphql/validation/ValidationErrorType.java index e701a5d778..59d5c3ac0f 100644 --- a/src/main/java/graphql/validation/ValidationErrorType.java +++ b/src/main/java/graphql/validation/ValidationErrorType.java @@ -2,10 +2,11 @@ import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; @PublicApi +@NullMarked public enum ValidationErrorType implements ValidationErrorClassification { - MaxValidationErrorsReached, DefaultForNonNullArgument, WrongType, From 6ec7d8219746db515b276673423ab13bdd2d129f Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:51:06 +0200 Subject: [PATCH 45/82] Annotate ValidationError --- .claude/commands/jspecify-annotate.md | 2 ++ src/main/java/graphql/validation/ValidationError.java | 10 ++++++++-- .../graphql/archunit/JSpecifyAnnotationsCheck.groovy | 3 --- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.claude/commands/jspecify-annotate.md b/.claude/commands/jspecify-annotate.md index aaa84e645a..fe6d4bc981 100644 --- a/.claude/commands/jspecify-annotate.md +++ b/.claude/commands/jspecify-annotate.md @@ -2,6 +2,8 @@ I have already asked IntelliJ to infer nullity on this class. Can you help me ma 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. + 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 diff --git a/src/main/java/graphql/validation/ValidationError.java b/src/main/java/graphql/validation/ValidationError.java index 04c1f88936..74f4640543 100644 --- a/src/main/java/graphql/validation/ValidationError.java +++ b/src/main/java/graphql/validation/ValidationError.java @@ -7,6 +7,8 @@ import graphql.GraphqlErrorHelper; import graphql.PublicApi; import graphql.language.SourceLocation; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; import java.util.ArrayList; import java.util.Collections; @@ -14,7 +16,10 @@ import java.util.Map; import java.util.stream.Collectors; +import static graphql.Assert.assertNotNull; + @PublicApi +@NullMarked public class ValidationError implements GraphQLError { private final List locations = new ArrayList<>(); @@ -24,8 +29,8 @@ public class ValidationError implements GraphQLError { private final ImmutableMap extensions; private ValidationError(Builder builder) { - this.validationErrorType = builder.validationErrorType; - this.description = builder.description; + this.validationErrorType = assertNotNull(builder.validationErrorType, () -> "validationErrorType cannot be null"); + this.description = assertNotNull(builder.description, () -> "description cannot be null"); if (builder.sourceLocations != null) { this.locations.addAll(builder.sourceLocations); } @@ -107,6 +112,7 @@ public static Builder newValidationError() { return new Builder(); } + @NullUnmarked public static class Builder { private List sourceLocations; private Map extensions; diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index fcfc1da46c..41eadb89c0 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -286,9 +286,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.util.TraverserContext", "graphql.util.TreeTransformer", "graphql.util.TreeTransformerUtil", - "graphql.validation.ValidationError", - "graphql.validation.ValidationErrorClassification", - "graphql.validation.ValidationErrorType", "graphql.validation.rules.DeferDirectiveLabel", "graphql.validation.rules.DeferDirectiveOnRootLevel", "graphql.validation.rules.DeferDirectiveOnValidOperation" From 98ff640f103f0ab9534ed33b6b91f23f773dfaad Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 20:16:52 +0200 Subject: [PATCH 46/82] Annotate NamedNode --- src/main/java/graphql/language/NamedNode.java | 2 ++ .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/language/NamedNode.java b/src/main/java/graphql/language/NamedNode.java index 1c6e54d03a..4e852dad45 100644 --- a/src/main/java/graphql/language/NamedNode.java +++ b/src/main/java/graphql/language/NamedNode.java @@ -2,11 +2,13 @@ import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; /** * Represents a language node that has a name */ @PublicApi +@NullMarked public interface NamedNode extends Node { /** diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 41eadb89c0..2559a5c0ef 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -141,7 +141,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.language.InterfaceTypeDefinition", "graphql.language.InterfaceTypeExtensionDefinition", "graphql.language.ListType", - "graphql.language.NamedNode", "graphql.language.Node", "graphql.language.NodeChildrenContainer", "graphql.language.NodeDirectivesBuilder", From 3f582650670abef21f29780e08255ecaa819dd6c Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 20:27:20 +0200 Subject: [PATCH 47/82] Annotate Argument --- src/main/java/graphql/language/Argument.java | 13 +++++++++---- .../archunit/JSpecifyAnnotationsCheck.groovy | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/graphql/language/Argument.java b/src/main/java/graphql/language/Argument.java index 9125d25f1f..d754e087a0 100644 --- a/src/main/java/graphql/language/Argument.java +++ b/src/main/java/graphql/language/Argument.java @@ -6,6 +6,9 @@ import graphql.PublicApi; import graphql.util.TraversalControl; import graphql.util.TraverserContext; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import java.util.LinkedHashMap; import java.util.List; @@ -19,6 +22,7 @@ import static graphql.language.NodeChildrenContainer.newNodeChildrenContainer; @PublicApi +@NullMarked public class Argument extends AbstractNode implements NamedNode { public static final String CHILD_VALUE = "value"; @@ -26,10 +30,10 @@ public class Argument extends AbstractNode implements NamedNode comments, IgnoredChars ignoredChars, Map additionalData) { + protected Argument(String name, Value value, @Nullable SourceLocation sourceLocation, List comments, IgnoredChars ignoredChars, Map additionalData) { super(sourceLocation, comments, ignoredChars, additionalData); this.name = name; - this.value = value; + this.value = assertNotNull(value, () -> "Argument value cannot be null"); } /** @@ -79,7 +83,7 @@ public Argument withNewChildren(NodeChildrenContainer newChildren) { } @Override - public boolean isEqualTo(Node o) { + public boolean isEqualTo(@Nullable Node o) { if (this == o) { return true; } @@ -95,7 +99,7 @@ public boolean isEqualTo(Node o) { @Override public Argument deepCopy() { - return new Argument(name, deepCopy(value), getSourceLocation(), getComments(), getIgnoredChars(), getAdditionalData()); + return new Argument(name, assertNotNull(deepCopy(value), "Argument value cannot be null"), getSourceLocation(), getComments(), getIgnoredChars(), getAdditionalData()); } @Override @@ -117,6 +121,7 @@ public Argument transform(Consumer builderConsumer) { return builder.build(); } + @NullUnmarked public static final class Builder implements NodeBuilder { private SourceLocation sourceLocation; private ImmutableList comments = emptyList(); diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 2559a5c0ef..bfcc295493 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -109,7 +109,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.introspection.IntrospectionWithDirectivesSupport", "graphql.introspection.IntrospectionWithDirectivesSupport\$DirectivePredicateEnvironment", "graphql.language.AbstractDescribedNode", - "graphql.language.Argument", "graphql.language.AstNodeAdapter", "graphql.language.AstPrinter", "graphql.language.AstSignature", @@ -285,6 +284,7 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.util.TraverserContext", "graphql.util.TreeTransformer", "graphql.util.TreeTransformerUtil", + // These classes will not be public API later, exempt here while marked as experimental "graphql.validation.rules.DeferDirectiveLabel", "graphql.validation.rules.DeferDirectiveOnRootLevel", "graphql.validation.rules.DeferDirectiveOnValidOperation" From eb980259a44b2c9312f100f4f780ca9c16ccc4bb Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 20:33:05 +0200 Subject: [PATCH 48/82] Annotate GraphQLTypeUtil --- src/main/java/graphql/schema/GraphQLTypeUtil.java | 2 ++ .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/GraphQLTypeUtil.java b/src/main/java/graphql/schema/GraphQLTypeUtil.java index ef933eb291..8300098870 100644 --- a/src/main/java/graphql/schema/GraphQLTypeUtil.java +++ b/src/main/java/graphql/schema/GraphQLTypeUtil.java @@ -5,6 +5,7 @@ import graphql.introspection.Introspection; import graphql.schema.idl.DirectiveInfo; import graphql.schema.idl.ScalarInfo; +import org.jspecify.annotations.NullMarked; import java.util.Stack; import java.util.function.Predicate; @@ -16,6 +17,7 @@ * A utility class that helps work with {@link graphql.schema.GraphQLType}s */ @PublicApi +@NullMarked public class GraphQLTypeUtil { /** diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index bfcc295493..4c829531df 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -224,7 +224,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.schema.GraphQLOutputType", "graphql.schema.GraphQLSchemaElement", "graphql.schema.GraphQLTypeReference", - "graphql.schema.GraphQLTypeUtil", "graphql.schema.GraphQLTypeVisitor", "graphql.schema.GraphQLTypeVisitorStub", "graphql.schema.GraphQLUnmodifiedType", From 97b407d8785c051620b473661fa3d111c9b932c6 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 20:54:06 +0200 Subject: [PATCH 49/82] Fix typo --- src/main/java/graphql/relay/Connection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/graphql/relay/Connection.java b/src/main/java/graphql/relay/Connection.java index a0ebf5d1dc..afbe5748e5 100644 --- a/src/main/java/graphql/relay/Connection.java +++ b/src/main/java/graphql/relay/Connection.java @@ -10,7 +10,7 @@ * This represents a connection in Relay, which is a list of {@link graphql.relay.Edge edge}s * as well as a {@link graphql.relay.PageInfo pageInfo} that describes the pagination of that list. *

- * See https://relay.dev/graphql/connections.htm + * See https://relay.dev/graphql/connections.htm */ @PublicApi @NullMarked From 44253f72d07a3c616b45108e159db88243225426 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 20:55:29 +0200 Subject: [PATCH 50/82] Update prompt --- .claude/commands/jspecify-annotate.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.claude/commands/jspecify-annotate.md b/.claude/commands/jspecify-annotate.md index fe6d4bc981..7fee15043c 100644 --- a/.claude/commands/jspecify-annotate.md +++ b/.claude/commands/jspecify-annotate.md @@ -18,4 +18,6 @@ Finally, please check all of 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. -Finally, can you remove this class from the JSpecifyAnnotationsCheck as an exemption. Thanks \ No newline at end of file +Finally, can you remove this class from the JSpecifyAnnotationsCheck as an exemption. Thanks + +You do not need to run the JSpecifyAnnotationsCheck. Removing the completed class is enough. From e8b167081277f6511f3146ae7ddb8198a0d4df38 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 20:57:23 +0200 Subject: [PATCH 51/82] Annotate Breadcrumb --- src/main/java/graphql/util/Breadcrumb.java | 5 ++++- .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/util/Breadcrumb.java b/src/main/java/graphql/util/Breadcrumb.java index ad1415a00a..4287b9df08 100644 --- a/src/main/java/graphql/util/Breadcrumb.java +++ b/src/main/java/graphql/util/Breadcrumb.java @@ -1,6 +1,8 @@ package graphql.util; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Objects; import java.util.StringJoiner; @@ -14,6 +16,7 @@ * @param the generic type of object */ @PublicApi +@NullMarked public class Breadcrumb { private final T node; @@ -33,7 +36,7 @@ public NodeLocation getLocation() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (this == o) { return true; } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 4c829531df..42bcbd1745 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -269,7 +269,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.schema.visibility.NoIntrospectionGraphqlFieldVisibility", "graphql.schema.visitor.GraphQLSchemaTraversalControl", "graphql.util.Anonymizer", - "graphql.util.Breadcrumb", "graphql.util.CyclicSchemaAnalyzer", "graphql.util.NodeAdapter", "graphql.util.NodeLocation", From f1b5529f969066eefeda2bddfb04766bb524010a Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 20:59:48 +0200 Subject: [PATCH 52/82] Annotate FieldComplexityCalculator --- src/main/java/graphql/analysis/FieldComplexityCalculator.java | 2 ++ .../groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/analysis/FieldComplexityCalculator.java b/src/main/java/graphql/analysis/FieldComplexityCalculator.java index c022dfd665..dc9c8ab67c 100644 --- a/src/main/java/graphql/analysis/FieldComplexityCalculator.java +++ b/src/main/java/graphql/analysis/FieldComplexityCalculator.java @@ -1,11 +1,13 @@ package graphql.analysis; import graphql.PublicApi; +import org.jspecify.annotations.NullMarked; /** * Used to calculate the complexity of a field. Used by {@link MaxQueryComplexityInstrumentation}. */ @PublicApi +@NullMarked @FunctionalInterface public interface FieldComplexityCalculator { diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 42bcbd1745..9530064dc5 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -12,7 +12,6 @@ class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ "graphql.TypeResolutionEnvironment", - "graphql.analysis.FieldComplexityCalculator", "graphql.analysis.FieldComplexityEnvironment", "graphql.analysis.MaxQueryComplexityInstrumentation", "graphql.analysis.MaxQueryDepthInstrumentation", From 230b757417b5dc0d8f6a7403a666468e6f4a301f Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 21:03:14 +0200 Subject: [PATCH 53/82] Annotate FieldComplexityEnvironment --- .../graphql/analysis/FieldComplexityEnvironment.java | 11 +++++++---- .../graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/graphql/analysis/FieldComplexityEnvironment.java b/src/main/java/graphql/analysis/FieldComplexityEnvironment.java index e813c56d75..8530b07a58 100644 --- a/src/main/java/graphql/analysis/FieldComplexityEnvironment.java +++ b/src/main/java/graphql/analysis/FieldComplexityEnvironment.java @@ -4,19 +4,22 @@ import graphql.language.Field; import graphql.schema.GraphQLCompositeType; import graphql.schema.GraphQLFieldDefinition; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Map; import java.util.Objects; @PublicApi +@NullMarked public class FieldComplexityEnvironment { private final Field field; private final GraphQLFieldDefinition fieldDefinition; private final GraphQLCompositeType parentType; - private final FieldComplexityEnvironment parentEnvironment; + private final @Nullable FieldComplexityEnvironment parentEnvironment; private final Map arguments; - public FieldComplexityEnvironment(Field field, GraphQLFieldDefinition fieldDefinition, GraphQLCompositeType parentType, Map arguments, FieldComplexityEnvironment parentEnvironment) { + public FieldComplexityEnvironment(Field field, GraphQLFieldDefinition fieldDefinition, GraphQLCompositeType parentType, Map arguments, @Nullable FieldComplexityEnvironment parentEnvironment) { this.field = field; this.fieldDefinition = fieldDefinition; this.parentType = parentType; @@ -36,7 +39,7 @@ public GraphQLCompositeType getParentType() { return parentType; } - public FieldComplexityEnvironment getParentEnvironment() { + public @Nullable FieldComplexityEnvironment getParentEnvironment() { return parentEnvironment; } @@ -55,7 +58,7 @@ public String toString() { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 9530064dc5..06e0acfc83 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -12,7 +12,6 @@ class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ "graphql.TypeResolutionEnvironment", - "graphql.analysis.FieldComplexityEnvironment", "graphql.analysis.MaxQueryComplexityInstrumentation", "graphql.analysis.MaxQueryDepthInstrumentation", "graphql.analysis.QueryComplexityCalculator", From ff81ff60d8c833e3b215b661a0f69902003fb034 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 21:11:42 +0200 Subject: [PATCH 54/82] Annotate TypeResolutionEnvironment --- src/main/java/graphql/TypeResolutionEnvironment.java | 11 +++++++---- .../graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/graphql/TypeResolutionEnvironment.java b/src/main/java/graphql/TypeResolutionEnvironment.java index ca57979bf5..535c55c449 100644 --- a/src/main/java/graphql/TypeResolutionEnvironment.java +++ b/src/main/java/graphql/TypeResolutionEnvironment.java @@ -7,17 +7,20 @@ import graphql.schema.DataFetchingFieldSelectionSet; import graphql.schema.GraphQLSchema; import graphql.schema.GraphQLType; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Map; import java.util.function.Supplier; /** * This is passed to a {@link graphql.schema.TypeResolver} to help with object type resolution. - * + *

* See {@link graphql.schema.TypeResolver#getType} for how this is used */ @SuppressWarnings("TypeParameterUnusedInFormals") @PublicApi +@NullMarked public class TypeResolutionEnvironment { private final Object object; @@ -52,7 +55,7 @@ public TypeResolutionEnvironment(TypeResolutionParameters parameters) { * @return the object that needs to be resolved into a specific graphql object type */ @SuppressWarnings("unchecked") - public T getObject() { + public @Nullable T getObject() { return (T) object; } @@ -95,7 +98,7 @@ public GraphQLSchema getSchema() { * @deprecated use {@link #getGraphQLContext()} instead */ @Deprecated(since = "2021-12-27") - public T getContext() { + public @Nullable T getContext() { //noinspection unchecked return (T) context; } @@ -114,7 +117,7 @@ public GraphQLContext getGraphQLContext() { * * @return the local context object */ - public T getLocalContext() { + public @Nullable T getLocalContext() { //noinspection unchecked return (T) localContext; } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index 06e0acfc83..cebc8eabc4 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -11,7 +11,6 @@ import spock.lang.Specification class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ - "graphql.TypeResolutionEnvironment", "graphql.analysis.MaxQueryComplexityInstrumentation", "graphql.analysis.MaxQueryDepthInstrumentation", "graphql.analysis.QueryComplexityCalculator", From ed2961fdc64800215f3f24dab4ead05f0bf22cce Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 21:16:29 +0200 Subject: [PATCH 55/82] Annotate MaxQueryComplexityInstrumentation --- .../analysis/MaxQueryComplexityInstrumentation.java | 9 +++++---- .../graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java b/src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java index 64a79612d6..d37f692039 100644 --- a/src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java +++ b/src/main/java/graphql/analysis/MaxQueryComplexityInstrumentation.java @@ -11,7 +11,7 @@ import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters; import graphql.validation.ValidationError; -import org.jspecify.annotations.Nullable; +import org.jspecify.annotations.NullMarked; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -29,6 +29,7 @@ * is exceeded. If the function returns {@code true} a {@link AbortExecutionException} is thrown. */ @PublicApi +@NullMarked public class MaxQueryComplexityInstrumentation extends SimplePerformantInstrumentation { private final int maxComplexity; @@ -79,12 +80,12 @@ public MaxQueryComplexityInstrumentation(int maxComplexity, FieldComplexityCalcu } @Override - public @Nullable CompletableFuture createStateAsync(InstrumentationCreateStateParameters parameters) { + public CompletableFuture createStateAsync(InstrumentationCreateStateParameters parameters) { return CompletableFuture.completedFuture(new State()); } @Override - public @Nullable InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters, InstrumentationState rawState) { + public InstrumentationContext> beginValidation(InstrumentationValidationParameters parameters, InstrumentationState rawState) { State state = ofState(rawState); // for API backwards compatibility reasons we capture the validation parameters, so we can put them into QueryComplexityInfo state.instrumentationValidationParameters.set(parameters); @@ -92,7 +93,7 @@ public MaxQueryComplexityInstrumentation(int maxComplexity, FieldComplexityCalcu } @Override - public @Nullable InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters instrumentationExecuteOperationParameters, InstrumentationState rawState) { + public InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters instrumentationExecuteOperationParameters, InstrumentationState rawState) { State state = ofState(rawState); QueryComplexityCalculator queryComplexityCalculator = newQueryComplexityCalculator(instrumentationExecuteOperationParameters.getExecutionContext()); int totalComplexity = queryComplexityCalculator.calculate(); diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index cebc8eabc4..e70735e93f 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -11,7 +11,6 @@ import spock.lang.Specification class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ - "graphql.analysis.MaxQueryComplexityInstrumentation", "graphql.analysis.MaxQueryDepthInstrumentation", "graphql.analysis.QueryComplexityCalculator", "graphql.analysis.QueryComplexityInfo", From cf724bf7d5de67e22b2e4d7baf171ea310c38d56 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 30 Aug 2025 21:21:16 +0200 Subject: [PATCH 56/82] Annotate MaxQueryDepthInstrumentation --- .claude/commands/jspecify-annotate.md | 2 ++ .../graphql/analysis/MaxQueryDepthInstrumentation.java | 7 ++++--- .../graphql/archunit/JSpecifyAnnotationsCheck.groovy | 1 - 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.claude/commands/jspecify-annotate.md b/.claude/commands/jspecify-annotate.md index 7fee15043c..259aa112e6 100644 --- a/.claude/commands/jspecify-annotate.md +++ b/.claude/commands/jspecify-annotate.md @@ -21,3 +21,5 @@ If you find NullAway errors, try and make the smallest possible change to fix th Finally, can you remove this class from the JSpecifyAnnotationsCheck as an exemption. Thanks You do not need to run the JSpecifyAnnotationsCheck. Removing the completed class is enough. + +Remember to delete all unused imports wehn you're done from the class you've just annotated. \ No newline at end of file diff --git a/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java b/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java index f242baf33d..6bab51da1a 100644 --- a/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java +++ b/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java @@ -8,6 +8,7 @@ import graphql.execution.instrumentation.InstrumentationState; import graphql.execution.instrumentation.SimplePerformantInstrumentation; import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import java.util.function.Function; @@ -21,9 +22,9 @@ * exceeded. If the function returns {@code true} a {@link AbortExecutionException} is thrown. */ @PublicApi +@NullMarked public class MaxQueryDepthInstrumentation extends SimplePerformantInstrumentation { - private final int maxDepth; private final Function maxQueryDepthExceededFunction; @@ -48,7 +49,7 @@ public MaxQueryDepthInstrumentation(int maxDepth, Function beginExecuteOperation(InstrumentationExecuteOperationParameters parameters, InstrumentationState state) { + public InstrumentationContext beginExecuteOperation(InstrumentationExecuteOperationParameters parameters, InstrumentationState state) { QueryTraverser queryTraverser = newQueryTraverser(parameters.getExecutionContext()); int depth = queryTraverser.reducePreOrder((env, acc) -> Math.max(getPathLength(env.getParentEnvironment()), acc), 0); if (depth > maxDepth) { @@ -84,7 +85,7 @@ QueryTraverser newQueryTraverser(ExecutionContext executionContext) { .build(); } - private int getPathLength(QueryVisitorFieldEnvironment path) { + private int getPathLength(@Nullable QueryVisitorFieldEnvironment path) { int length = 1; while (path != null) { path = path.getParentEnvironment(); diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index e70735e93f..dc32f18817 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -11,7 +11,6 @@ import spock.lang.Specification class JSpecifyAnnotationsCheck extends Specification { private static final Set JSPECIFY_EXEMPTION_LIST = [ - "graphql.analysis.MaxQueryDepthInstrumentation", "graphql.analysis.QueryComplexityCalculator", "graphql.analysis.QueryComplexityInfo", "graphql.analysis.QueryDepthInfo", From 8a47304cabdbc472c0046c7641d6796bfd77695a Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 31 Aug 2025 19:54:24 +0200 Subject: [PATCH 57/82] Make ValidationError description nullable as message is nullable --- src/main/java/graphql/validation/ValidationError.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/validation/ValidationError.java b/src/main/java/graphql/validation/ValidationError.java index 74f4640543..1c20206805 100644 --- a/src/main/java/graphql/validation/ValidationError.java +++ b/src/main/java/graphql/validation/ValidationError.java @@ -9,6 +9,7 @@ import graphql.language.SourceLocation; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; @@ -23,14 +24,14 @@ public class ValidationError implements GraphQLError { private final List locations = new ArrayList<>(); - private final String description; + private final @Nullable String description; private final ValidationErrorClassification validationErrorType; private final List queryPath = new ArrayList<>(); private final ImmutableMap extensions; private ValidationError(Builder builder) { this.validationErrorType = assertNotNull(builder.validationErrorType, () -> "validationErrorType cannot be null"); - this.description = assertNotNull(builder.description, () -> "description cannot be null"); + this.description = builder.description; if (builder.sourceLocations != null) { this.locations.addAll(builder.sourceLocations); } @@ -47,10 +48,12 @@ public ValidationErrorClassification getValidationErrorType() { } @Override + @Nullable public String getMessage() { return description; } + @Nullable public String getDescription() { return description; } From de6ad6694d26c680fc8d06ed321c654d6ba1aa27 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 31 Aug 2025 20:04:11 +0200 Subject: [PATCH 58/82] Add Nullmarked --- src/main/java/graphql/ParseAndValidateResult.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/graphql/ParseAndValidateResult.java b/src/main/java/graphql/ParseAndValidateResult.java index ea143364c4..fe7a70487e 100644 --- a/src/main/java/graphql/ParseAndValidateResult.java +++ b/src/main/java/graphql/ParseAndValidateResult.java @@ -5,6 +5,7 @@ import graphql.language.Document; import graphql.parser.InvalidSyntaxException; import graphql.validation.ValidationError; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; @@ -18,6 +19,7 @@ * and validate operation. */ @PublicApi +@NullMarked public class ParseAndValidateResult { private final @Nullable Document document; From fd3b4af210f5f95c33496447bd96650059fd0a12 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 31 Aug 2025 20:04:42 +0200 Subject: [PATCH 59/82] Revert to nullable description and type for ValidationError --- .../java/graphql/validation/ValidationError.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/graphql/validation/ValidationError.java b/src/main/java/graphql/validation/ValidationError.java index 1c20206805..a28201879c 100644 --- a/src/main/java/graphql/validation/ValidationError.java +++ b/src/main/java/graphql/validation/ValidationError.java @@ -17,20 +17,18 @@ import java.util.Map; import java.util.stream.Collectors; -import static graphql.Assert.assertNotNull; - @PublicApi @NullMarked public class ValidationError implements GraphQLError { private final List locations = new ArrayList<>(); private final @Nullable String description; - private final ValidationErrorClassification validationErrorType; + private final @Nullable ValidationErrorClassification validationErrorType; private final List queryPath = new ArrayList<>(); private final ImmutableMap extensions; private ValidationError(Builder builder) { - this.validationErrorType = assertNotNull(builder.validationErrorType, () -> "validationErrorType cannot be null"); + this.validationErrorType = builder.validationErrorType; this.description = builder.description; if (builder.sourceLocations != null) { this.locations.addAll(builder.sourceLocations); @@ -43,18 +41,16 @@ private ValidationError(Builder builder) { this.extensions = (builder.extensions != null) ? ImmutableMap.copyOf(builder.extensions) : ImmutableMap.of(); } - public ValidationErrorClassification getValidationErrorType() { + public @Nullable ValidationErrorClassification getValidationErrorType() { return validationErrorType; } @Override - @Nullable - public String getMessage() { + public @Nullable String getMessage() { return description; } - @Nullable - public String getDescription() { + public @Nullable String getDescription() { return description; } From 71ef68045ff0a5fbc013c74ade9deec500166dfc Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 31 Aug 2025 20:24:16 +0200 Subject: [PATCH 60/82] Update test for Argument, and make nonnullable field clearer --- src/main/java/graphql/language/Argument.java | 4 ++-- src/test/groovy/graphql/language/NodeVisitorStubTest.groovy | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/graphql/language/Argument.java b/src/main/java/graphql/language/Argument.java index d754e087a0..4cf993d99c 100644 --- a/src/main/java/graphql/language/Argument.java +++ b/src/main/java/graphql/language/Argument.java @@ -32,7 +32,7 @@ public class Argument extends AbstractNode implements NamedNode comments, IgnoredChars ignoredChars, Map additionalData) { super(sourceLocation, comments, ignoredChars, additionalData); - this.name = name; + this.name = assertNotNull(name, () -> "Argument name cannot be null"); this.value = assertNotNull(value, () -> "Argument value cannot be null"); } @@ -99,7 +99,7 @@ public boolean isEqualTo(@Nullable Node o) { @Override public Argument deepCopy() { - return new Argument(name, assertNotNull(deepCopy(value), "Argument value cannot be null"), getSourceLocation(), getComments(), getIgnoredChars(), getAdditionalData()); + return new Argument(assertNotNull(name, "Argument name cannot be null"), assertNotNull(deepCopy(value), "Argument value cannot be null"), getSourceLocation(), getComments(), getIgnoredChars(), getAdditionalData()); } @Override diff --git a/src/test/groovy/graphql/language/NodeVisitorStubTest.groovy b/src/test/groovy/graphql/language/NodeVisitorStubTest.groovy index 8ca86ca936..df165b23cd 100644 --- a/src/test/groovy/graphql/language/NodeVisitorStubTest.groovy +++ b/src/test/groovy/graphql/language/NodeVisitorStubTest.groovy @@ -132,7 +132,7 @@ class NodeVisitorStubTest extends Specification { where: node | visitMethod - new Argument("", null) | 'visitArgument' + new Argument("myArgument", NullValue.of()) | 'visitArgument' new Directive("", emptyList()) | 'visitDirective' new DirectiveLocation("") | 'visitDirectiveLocation' Document.newDocument().build() | 'visitDocument' From 23afaea004c146b6f8dfd7de3346566254bfa8af Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 1 Sep 2025 14:11:10 +0200 Subject: [PATCH 61/82] Annotate Anonymizer --- src/main/java/graphql/util/Anonymizer.java | 16 ++++++++-------- .../archunit/JSpecifyAnnotationsCheck.groovy | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/java/graphql/util/Anonymizer.java b/src/main/java/graphql/util/Anonymizer.java index 11b3f30309..fd321bb6df 100644 --- a/src/main/java/graphql/util/Anonymizer.java +++ b/src/main/java/graphql/util/Anonymizer.java @@ -75,6 +75,7 @@ import graphql.schema.idl.ScalarInfo; import graphql.schema.idl.TypeUtil; import graphql.schema.impl.SchemaUtil; +import org.jspecify.annotations.NullMarked; import java.math.BigInteger; import java.util.ArrayList; @@ -91,6 +92,7 @@ import java.util.function.Consumer; import static graphql.Assert.assertNotNull; +import static graphql.Assert.assertShouldNeverHappen; import static graphql.parser.ParserEnvironment.newParserEnvironment; import static graphql.schema.GraphQLNonNull.nonNull; import static graphql.schema.GraphQLTypeUtil.unwrapNonNull; @@ -105,6 +107,7 @@ * into anonymized schemas and queries. */ @PublicApi +@NullMarked public class Anonymizer { public static class AnonymizeResult { @@ -393,7 +396,7 @@ private static Value replaceValue(Value valueLiteral, GraphQLInputType argType, } else if (valueLiteral instanceof EnumValue) { GraphQLEnumType enumType = unwrapNonNullAs(argType); GraphQLEnumValueDefinition enumValueDefinition = enumType.getValue(((EnumValue) valueLiteral).getName()); - String newName = newNameMap.get(enumValueDefinition); + String newName = assertNotNull(newNameMap.get(enumValueDefinition), "No new name found for enum value %s", ((EnumValue) valueLiteral).getName()); return new EnumValue(newName); } else if (valueLiteral instanceof ObjectValue) { GraphQLInputObjectType inputObjectType = unwrapNonNullAs(argType); @@ -659,7 +662,7 @@ private static void getSameFieldsImpl(String fieldName, alreadyChecked.add(curObjectOrInterface); // "up": get all Interfaces - GraphQLImplementingType type = (GraphQLImplementingType) schema.getType(curObjectOrInterface); + GraphQLImplementingType type = assertNotNull((GraphQLImplementingType) schema.getType(curObjectOrInterface), "No type found for %s", curObjectOrInterface); List interfaces = type.getInterfaces(); getMatchingFieldDefinitions(fieldName, interfaces, result); for (GraphQLNamedOutputType interfaze : interfaces) { @@ -905,15 +908,13 @@ private static GraphQLType fromTypeToGraphQLType(Type type, GraphQLSchema schema if (type instanceof TypeName) { String typeName = ((TypeName) type).getName(); GraphQLType graphQLType = schema.getType(typeName); - graphql.Assert.assertNotNull(graphQLType, "Schema must contain type %s", typeName); - return graphQLType; + return assertNotNull(graphQLType, "Schema must contain type %s", typeName); } else if (type instanceof NonNullType) { return nonNull(fromTypeToGraphQLType(TypeUtil.unwrapOne(type), schema)); } else if (type instanceof ListType) { return GraphQLList.list(fromTypeToGraphQLType(TypeUtil.unwrapOne(type), schema)); } else { - graphql.Assert.assertShouldNeverHappen(); - return null; + return assertShouldNeverHappen(); } } @@ -926,8 +927,7 @@ private static Type replaceTypeName(Type type, String newName) { } else if (type instanceof NonNullType) { return NonNullType.newNonNullType(replaceTypeName(((NonNullType) type).getType(), newName)).build(); } else { - graphql.Assert.assertShouldNeverHappen(); - return null; + return assertShouldNeverHappen(); } } diff --git a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy index dc32f18817..994c835aab 100644 --- a/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy +++ b/src/test/groovy/graphql/archunit/JSpecifyAnnotationsCheck.groovy @@ -263,7 +263,6 @@ class JSpecifyAnnotationsCheck extends Specification { "graphql.schema.visibility.GraphqlFieldVisibility", "graphql.schema.visibility.NoIntrospectionGraphqlFieldVisibility", "graphql.schema.visitor.GraphQLSchemaTraversalControl", - "graphql.util.Anonymizer", "graphql.util.CyclicSchemaAnalyzer", "graphql.util.NodeAdapter", "graphql.util.NodeLocation", From 09a52367743f12dca95fcc40b5d4dabb6789a580 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 4 Dec 2025 14:26:58 +1100 Subject: [PATCH 62/82] Use string literal optimisation for assert --- .claude/commands/jspecify-annotate.md | 2 +- src/main/java/graphql/GraphQL.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude/commands/jspecify-annotate.md b/.claude/commands/jspecify-annotate.md index 259aa112e6..fa9c345ded 100644 --- a/.claude/commands/jspecify-annotate.md +++ b/.claude/commands/jspecify-annotate.md @@ -22,4 +22,4 @@ Finally, can you remove this class from the JSpecifyAnnotationsCheck as an exemp You do not need to run the JSpecifyAnnotationsCheck. Removing the completed class is enough. -Remember to delete all unused imports wehn you're done from the class you've just annotated. \ No newline at end of file +Remember to delete all unused imports when you're done from the class you've just annotated. \ No newline at end of file diff --git a/src/main/java/graphql/GraphQL.java b/src/main/java/graphql/GraphQL.java index 2c1976ba84..1d99ad0377 100644 --- a/src/main/java/graphql/GraphQL.java +++ b/src/main/java/graphql/GraphQL.java @@ -559,14 +559,14 @@ private PreparsedDocumentEntry parseAndValidate(AtomicReference ParseAndValidateResult parseResult = parse(executionInput, graphQLSchema, instrumentationState); if (parseResult.isFailure()) { - return new PreparsedDocumentEntry(assertNotNull(parseResult.getSyntaxException(), () -> "Parse result syntax exception cannot be null when failed").toInvalidSyntaxError()); + return new PreparsedDocumentEntry(assertNotNull(parseResult.getSyntaxException(), "Parse result syntax exception cannot be null when failed").toInvalidSyntaxError()); } else { final Document document = parseResult.getDocument(); // they may have changed the document and the variables via instrumentation so update the reference to it executionInput = executionInput.transform(builder -> builder.variables(parseResult.getVariables())); executionInputRef.set(executionInput); - final List errors = validate(executionInput, assertNotNull(document, () -> "Document cannot be null when parse succeeded"), graphQLSchema, instrumentationState); + final List errors = validate(executionInput, assertNotNull(document, "Document cannot be null when parse succeeded"), graphQLSchema, instrumentationState); if (!errors.isEmpty()) { return new PreparsedDocumentEntry(document, errors); } From d1e2e54f3ea31336e3d708919929f7d9e515696c Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 4 Dec 2025 14:34:19 +1100 Subject: [PATCH 63/82] Tidy up --- src/main/java/graphql/ParseAndValidate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/graphql/ParseAndValidate.java b/src/main/java/graphql/ParseAndValidate.java index 9d23030977..0e7f1a2b21 100644 --- a/src/main/java/graphql/ParseAndValidate.java +++ b/src/main/java/graphql/ParseAndValidate.java @@ -47,7 +47,7 @@ public class ParseAndValidate { public static ParseAndValidateResult parseAndValidate(GraphQLSchema graphQLSchema, ExecutionInput executionInput) { ParseAndValidateResult result = parse(executionInput); if (!result.isFailure()) { - List errors = validate(graphQLSchema, assertNotNull(result.getDocument(), () -> "Parse result document cannot be null when parse succeeded"), executionInput.getLocale()); + List errors = validate(graphQLSchema, assertNotNull(result.getDocument(), "Parse result document cannot be null when parse succeeded"), executionInput.getLocale()); return result.transform(builder -> builder.validationErrors(errors)); } return result; From 95e7dc9bbe8ef733b33003b49de07f0161a7eba6 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 4 Dec 2025 14:48:06 +1100 Subject: [PATCH 64/82] Make path length not nullable --- .../java/graphql/analysis/MaxQueryDepthInstrumentation.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java b/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java index 6bab51da1a..1ac22227f8 100644 --- a/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java +++ b/src/main/java/graphql/analysis/MaxQueryDepthInstrumentation.java @@ -9,7 +9,6 @@ import graphql.execution.instrumentation.SimplePerformantInstrumentation; import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters; import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; import java.util.function.Function; @@ -85,7 +84,7 @@ QueryTraverser newQueryTraverser(ExecutionContext executionContext) { .build(); } - private int getPathLength(@Nullable QueryVisitorFieldEnvironment path) { + private int getPathLength(QueryVisitorFieldEnvironment path) { int length = 1; while (path != null) { path = path.getParentEnvironment(); From f8920d893cb5b4425f8a64caa35d04779edbabcc Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 4 Dec 2025 14:49:26 +1100 Subject: [PATCH 65/82] Use string literal for assert messages --- src/main/java/graphql/language/Argument.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/language/Argument.java b/src/main/java/graphql/language/Argument.java index 4cf993d99c..885c02bcdd 100644 --- a/src/main/java/graphql/language/Argument.java +++ b/src/main/java/graphql/language/Argument.java @@ -32,8 +32,8 @@ public class Argument extends AbstractNode implements NamedNode comments, IgnoredChars ignoredChars, Map additionalData) { super(sourceLocation, comments, ignoredChars, additionalData); - this.name = assertNotNull(name, () -> "Argument name cannot be null"); - this.value = assertNotNull(value, () -> "Argument value cannot be null"); + this.name = assertNotNull(name, "Argument name cannot be null"); + this.value = assertNotNull(value, "Argument value cannot be null"); } /** From c78124cb3eacadbdd071fbd72021ba106ae14b9a Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 7 Dec 2025 15:00:17 +1100 Subject: [PATCH 66/82] Change GraphQL error message to be non-nullable --- src/main/java/graphql/GraphQLError.java | 6 +++++- src/main/java/graphql/validation/ValidationError.java | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/graphql/GraphQLError.java b/src/main/java/graphql/GraphQLError.java index 948e798284..3ea8503bca 100644 --- a/src/main/java/graphql/GraphQLError.java +++ b/src/main/java/graphql/GraphQLError.java @@ -27,8 +27,12 @@ public interface GraphQLError extends Serializable { /** * @return a description of the error intended for the developer as a guide to understand and correct the error + * + * Non-nullable from the spec: + * Every error must contain an entry with the key "message" with a string description of the error intended for + * the developer as a guide to understand and correct the error. */ - @Nullable String getMessage(); + String getMessage(); /** * @return the location(s) within the GraphQL document at which the error occurred. Each {@link SourceLocation} diff --git a/src/main/java/graphql/validation/ValidationError.java b/src/main/java/graphql/validation/ValidationError.java index a28201879c..3fd0001c33 100644 --- a/src/main/java/graphql/validation/ValidationError.java +++ b/src/main/java/graphql/validation/ValidationError.java @@ -22,7 +22,7 @@ public class ValidationError implements GraphQLError { private final List locations = new ArrayList<>(); - private final @Nullable String description; + private final String description; private final @Nullable ValidationErrorClassification validationErrorType; private final List queryPath = new ArrayList<>(); private final ImmutableMap extensions; @@ -46,11 +46,11 @@ private ValidationError(Builder builder) { } @Override - public @Nullable String getMessage() { + public String getMessage() { return description; } - public @Nullable String getDescription() { + public String getDescription() { return description; } From 5b7104e06aaa3253133acfef1d99891e7971190b Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 7 Dec 2025 15:33:11 +1100 Subject: [PATCH 67/82] Improve prompt --- .claude/commands/jspecify-annotate.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.claude/commands/jspecify-annotate.md b/.claude/commands/jspecify-annotate.md index fa9c345ded..97fb977fe2 100644 --- a/.claude/commands/jspecify-annotate.md +++ b/.claude/commands/jspecify-annotate.md @@ -9,17 +9,26 @@ Analyze this Java class and add JSpecify annotations based on: 2. Remove all the redundant `@NonNull` annotations that IntelliJ added 3. Check Javadoc @param tags mentioning "null", "nullable", "may be null" 4. Check Javadoc @return tags mentioning "null", "optional", "if available" -5. GraphQL specification semantics (nullable fields, non-null by default) -6. Method implementations that return null or check for null +5. Method implementations that return null or check for null +6. GraphQL specification details (see details below) IntelliJ's infer nullity code analysis isn't comprehensive so feel free to make corrections. -Finally, please check all of this works, by running the NullAway compile check. +## 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: + +The spec defines which elements are required (non-null) vs optional (nullable). Look for keywords like "MUST" to indicate when an element is required, and conditional words such as "IF" to indicate when an element is optional. + +If a class implements or represents a GraphQL specification concept, prioritize the spec's nullability requirements over what IntelliJ inferred. + +## How to validate +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. -Finally, can you remove this class from the JSpecifyAnnotationsCheck as an exemption. Thanks +## Cleaning up +Finally, can you remove this class from the JSpecifyAnnotationsCheck as an exemption You do not need to run the JSpecifyAnnotationsCheck. Removing the completed class is enough. -Remember to delete all unused imports when you're done from the class you've just annotated. \ No newline at end of file +Remember to delete all unused imports when you're done from the class you've just annotated. From de00e30fa89c59cfe8b3aa9e5c87089250722ba8 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:49:52 +1100 Subject: [PATCH 68/82] Add clarification on generics type arguments --- .claude/commands/jspecify-annotate.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.claude/commands/jspecify-annotate.md b/.claude/commands/jspecify-annotate.md index 97fb977fe2..932003ef09 100644 --- a/.claude/commands/jspecify-annotate.md +++ b/.claude/commands/jspecify-annotate.md @@ -32,3 +32,26 @@ Finally, can you remove this class from the JSpecifyAnnotationsCheck as an exemp You do not need to run the JSpecifyAnnotationsCheck. Removing the completed class is enough. Remember to delete all unused imports when you're done from the class you've just annotated. + +## Generics Annotations + +When annotating generic types and methods, follow these JSpecify rules: + +### Type Parameter Bounds + +The bound on a type parameter determines whether nullable type arguments are allowed: + +| Declaration | Allows `@Nullable` type argument? | +|-------------|----------------------------------| +| `` | ❌ No — `Box<@Nullable String>` is illegal | +| `` | ✅ Yes — `Box<@Nullable String>` is legal | + +**When to use ``:** +- When callers genuinely need to parameterize with nullable types +- Example: `DataFetcherResult` — data fetchers may return nullable types + +**When to keep ``:** +- 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 + From d5de341a98df93168b64045988493bd2c5a0d9c9 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:23:34 +1100 Subject: [PATCH 69/82] Make TypeResolutionEnvironment value nullable --- src/main/java/graphql/TypeResolutionEnvironment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/graphql/TypeResolutionEnvironment.java b/src/main/java/graphql/TypeResolutionEnvironment.java index 535c55c449..b3755fb90c 100644 --- a/src/main/java/graphql/TypeResolutionEnvironment.java +++ b/src/main/java/graphql/TypeResolutionEnvironment.java @@ -23,7 +23,7 @@ @NullMarked public class TypeResolutionEnvironment { - private final Object object; + private final @Nullable Object object; private final Supplier> arguments; private final MergedField field; private final GraphQLType fieldType; From 0496a3d2f2b86750b76a5eefcc8267c4d54c1e76 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:26:06 +1100 Subject: [PATCH 70/82] Annotate TypeResolutionParameters --- .../java/graphql/execution/TypeResolutionParameters.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/graphql/execution/TypeResolutionParameters.java b/src/main/java/graphql/execution/TypeResolutionParameters.java index 4f20fac4fd..dd648edb61 100644 --- a/src/main/java/graphql/execution/TypeResolutionParameters.java +++ b/src/main/java/graphql/execution/TypeResolutionParameters.java @@ -7,6 +7,8 @@ import graphql.schema.DataFetchingFieldSelectionSet; import graphql.schema.GraphQLSchema; import graphql.schema.GraphQLType; +import org.jspecify.annotations.Nullable; +import org.jspecify.annotations.NullUnmarked; import java.util.Map; import java.util.function.Supplier; @@ -20,7 +22,7 @@ public class TypeResolutionParameters { private final MergedField field; private final GraphQLType fieldType; - private final Object value; + private final @Nullable Object value; private final Supplier> argumentValues; private final GraphQLSchema schema; private final Object context; @@ -48,7 +50,7 @@ public GraphQLType getFieldType() { return fieldType; } - public Object getValue() { + public @Nullable Object getValue() { return value; } @@ -86,6 +88,7 @@ public Object getLocalContext() { return localContext; } + @NullUnmarked public static class Builder { private MergedField field; From d2983dab21bdf974ae21c33ebd9b8e072899918e Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:28:31 +1100 Subject: [PATCH 71/82] Add Nullmarked to TypeResolutionParameters --- src/main/java/graphql/execution/TypeResolutionParameters.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/graphql/execution/TypeResolutionParameters.java b/src/main/java/graphql/execution/TypeResolutionParameters.java index dd648edb61..fa99f449dd 100644 --- a/src/main/java/graphql/execution/TypeResolutionParameters.java +++ b/src/main/java/graphql/execution/TypeResolutionParameters.java @@ -7,6 +7,7 @@ import graphql.schema.DataFetchingFieldSelectionSet; import graphql.schema.GraphQLSchema; import graphql.schema.GraphQLType; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.jspecify.annotations.NullUnmarked; @@ -18,6 +19,7 @@ * but for legacy reasons was not. So it acts as the builder of {@link TypeResolutionEnvironment} objects */ @Internal +@NullMarked public class TypeResolutionParameters { private final MergedField field; From 92b3ac1d6059fcfefd8a1991b3570568307ba0eb Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:45:27 +1100 Subject: [PATCH 72/82] Add prompt to reduce whitespace diff --- .claude/commands/jspecify-annotate.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.claude/commands/jspecify-annotate.md b/.claude/commands/jspecify-annotate.md index 932003ef09..48d385ea1a 100644 --- a/.claude/commands/jspecify-annotate.md +++ b/.claude/commands/jspecify-annotate.md @@ -26,6 +26,10 @@ 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. +## Formatting Guidelines + +Do not make spacing or formatting changes. Avoid adjusting whitespace, line breaks, or other formatting when editing code. These changes make diffs messy and harder to review. Only make the minimal changes necessary to accomplish the task. + ## Cleaning up Finally, can you remove this class from the JSpecifyAnnotationsCheck as an exemption From 0bf48d3484975cd5f1560427aa8f5d8c28246f14 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:12:49 +1100 Subject: [PATCH 73/82] Prompt updates --- .claude/commands/jspecify-annotate.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.claude/commands/jspecify-annotate.md b/.claude/commands/jspecify-annotate.md index 48d385ea1a..7083a6b3da 100644 --- a/.claude/commands/jspecify-annotate.md +++ b/.claude/commands/jspecify-annotate.md @@ -1,4 +1,4 @@ -I have already asked IntelliJ to infer nullity on this class. Can you help me make this more accurate. +The task is to annotate public API classes (marked with `@PublicAPI`) with JSpecify nullability annotations. Note that JSpecify is already used in this repository so it's already imported. @@ -12,8 +12,6 @@ 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) -IntelliJ's infer nullity code analysis isn't comprehensive so feel free to make corrections. - ## 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: From 29315404c1edec92d11504d9ac4ee6565b56493f Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:13:15 +1100 Subject: [PATCH 74/82] Adjulst description of validation error to be nonnull --- src/main/java/graphql/validation/ValidationError.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/graphql/validation/ValidationError.java b/src/main/java/graphql/validation/ValidationError.java index 3fd0001c33..c4830f729e 100644 --- a/src/main/java/graphql/validation/ValidationError.java +++ b/src/main/java/graphql/validation/ValidationError.java @@ -1,6 +1,5 @@ package graphql.validation; - import com.google.common.collect.ImmutableMap; import graphql.ErrorType; import graphql.GraphQLError; @@ -17,6 +16,8 @@ import java.util.Map; import java.util.stream.Collectors; +import static graphql.Assert.assertNotNull; + @PublicApi @NullMarked public class ValidationError implements GraphQLError { @@ -29,7 +30,7 @@ public class ValidationError implements GraphQLError { private ValidationError(Builder builder) { this.validationErrorType = builder.validationErrorType; - this.description = builder.description; + this.description = assertNotNull(builder.description, "description is required"); if (builder.sourceLocations != null) { this.locations.addAll(builder.sourceLocations); } @@ -106,7 +107,6 @@ public int hashCode() { return GraphqlErrorHelper.hashCode(this); } - public static Builder newValidationError() { return new Builder(); } @@ -119,7 +119,6 @@ public static class Builder { private ValidationErrorClassification validationErrorType; private List queryPath; - public Builder validationErrorType(ValidationErrorClassification validationErrorType) { this.validationErrorType = validationErrorType; return this; From 66f3fa0681952b4c5fc6c87d6bff1756d307d5a6 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:14:40 +1100 Subject: [PATCH 75/82] Adjust tests to be compliant with non-nullable description --- .../groovy/graphql/execution/DataFetcherResultTest.groovy | 2 +- .../ExecutionStrategyExceptionHandlingEquivalenceTest.groovy | 2 +- .../execution/preparsed/PreparsedDocumentEntryTest.groovy | 2 +- .../groovy/graphql/validation/ValidationErrorToString.groovy | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/test/groovy/graphql/execution/DataFetcherResultTest.groovy b/src/test/groovy/graphql/execution/DataFetcherResultTest.groovy index 07318afa75..47d87b991f 100644 --- a/src/test/groovy/graphql/execution/DataFetcherResultTest.groovy +++ b/src/test/groovy/graphql/execution/DataFetcherResultTest.groovy @@ -8,7 +8,7 @@ import spock.lang.Specification class DataFetcherResultTest extends Specification { - def error1 = ValidationError.newValidationError().validationErrorType(ValidationErrorType.DuplicateOperationName).build() + def error1 = ValidationError.newValidationError().validationErrorType(ValidationErrorType.DuplicateOperationName).description("Duplicate operation name").build() def error2 = new InvalidSyntaxError([], "Boo") def "basic building"() { diff --git a/src/test/groovy/graphql/execution/ExecutionStrategyExceptionHandlingEquivalenceTest.groovy b/src/test/groovy/graphql/execution/ExecutionStrategyExceptionHandlingEquivalenceTest.groovy index 2c8653491e..a154c12e92 100644 --- a/src/test/groovy/graphql/execution/ExecutionStrategyExceptionHandlingEquivalenceTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionStrategyExceptionHandlingEquivalenceTest.groovy @@ -18,7 +18,7 @@ class ExecutionStrategyExceptionHandlingEquivalenceTest extends Specification { @Override InstrumentationContext beginFieldFetch(InstrumentationFieldFetchParameters parameters, InstrumentationState state) { - throw new AbortExecutionException([ValidationError.newValidationError().validationErrorType(ValidationErrorType.UnknownType).build()]) + throw new AbortExecutionException([ValidationError.newValidationError().validationErrorType(ValidationErrorType.UnknownType).description("Unknown type encountered").build()]) } } diff --git a/src/test/groovy/graphql/execution/preparsed/PreparsedDocumentEntryTest.groovy b/src/test/groovy/graphql/execution/preparsed/PreparsedDocumentEntryTest.groovy index 4cfa7d0e15..423b557b7c 100644 --- a/src/test/groovy/graphql/execution/preparsed/PreparsedDocumentEntryTest.groovy +++ b/src/test/groovy/graphql/execution/preparsed/PreparsedDocumentEntryTest.groovy @@ -33,7 +33,7 @@ class PreparsedDocumentEntryTest extends Specification { def "Ensure a non-null errors returns"() { given: def errors = [new InvalidSyntaxError(new SourceLocation(0, 0), "bang"), - ValidationError.newValidationError().validationErrorType(ValidationErrorType.InvalidSyntax).build()] + ValidationError.newValidationError().validationErrorType(ValidationErrorType.InvalidSyntax).description("Invalid syntax in document").build()] when: def docEntry = new PreparsedDocumentEntry(errors) diff --git a/src/test/groovy/graphql/validation/ValidationErrorToString.groovy b/src/test/groovy/graphql/validation/ValidationErrorToString.groovy index cfade59c28..e3f40428b8 100644 --- a/src/test/groovy/graphql/validation/ValidationErrorToString.groovy +++ b/src/test/groovy/graphql/validation/ValidationErrorToString.groovy @@ -27,13 +27,14 @@ class ValidationErrorToString extends Specification { validationError.toString() == "ValidationError{validationErrorType=UnknownType, queryPath=[home, address], message=Validation Error (UnknownType), locations=[SourceLocation{line=5, column=0}, SourceLocation{line=10, column=1}], description='Validation Error (UnknownType)', extensions=[extension1=first, extension2=true, extension3=2]}" } - def 'toString prints correctly ValidationError object when all fields are empty'() { + def 'toString prints correctly ValidationError object when optional fields are empty'() { when: def validationError = ValidationError .newValidationError() + .description("Test error") .build() then: - validationError.toString() == "ValidationError{validationErrorType=null, queryPath=[], message=null, locations=[], description='null', extensions=[]}" + validationError.toString() == "ValidationError{validationErrorType=null, queryPath=[], message=Test error, locations=[], description='Test error', extensions=[]}" } } From cd232df48b08990d5447caa3381dcbe9869b315f Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:25:15 +1100 Subject: [PATCH 76/82] Update error message --- src/main/java/graphql/relay/SimpleListConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/graphql/relay/SimpleListConnection.java b/src/main/java/graphql/relay/SimpleListConnection.java index 07b630bec7..8ae3afd7eb 100644 --- a/src/main/java/graphql/relay/SimpleListConnection.java +++ b/src/main/java/graphql/relay/SimpleListConnection.java @@ -28,7 +28,7 @@ public class SimpleListConnection implements TrivialDataFetcher public SimpleListConnection(List data, String prefix) { this.data = assertNotNull(data, " data cannot be null"); - assertTrue(!prefix.isEmpty(), "prefix cannot be null or empty"); + assertTrue(!prefix.isEmpty(), "prefix cannot be empty"); this.prefix = prefix; } From b8b8a5a00eb3bd4de7454ed31bcee33b73a04da1 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:28:48 +1100 Subject: [PATCH 77/82] Update tests given classiciation is not nullable --- src/test/groovy/graphql/GraphQLErrorTest.groovy | 2 +- src/test/groovy/graphql/GraphqlErrorHelperTest.groovy | 2 +- .../groovy/graphql/execution/AbortExecutionExceptionTest.groovy | 2 +- .../execution/DataFetcherExceptionHandlerResultTest.groovy | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/groovy/graphql/GraphQLErrorTest.groovy b/src/test/groovy/graphql/GraphQLErrorTest.groovy index cf80eb845f..165c49cea3 100644 --- a/src/test/groovy/graphql/GraphQLErrorTest.groovy +++ b/src/test/groovy/graphql/GraphQLErrorTest.groovy @@ -129,7 +129,7 @@ class GraphQLErrorTest extends Specification { @Override ErrorType getErrorType() { - return null + return ErrorType.DataFetchingException } } diff --git a/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy b/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy index a0c4c4e9e3..57052dcdde 100644 --- a/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy +++ b/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy @@ -55,7 +55,7 @@ class GraphqlErrorHelperTest extends Specification { @Override ErrorClassification getErrorType() { - return null + return ErrorType.DataFetchingException } @Override diff --git a/src/test/groovy/graphql/execution/AbortExecutionExceptionTest.groovy b/src/test/groovy/graphql/execution/AbortExecutionExceptionTest.groovy index da2c9b8cc4..0f5f3ab47c 100644 --- a/src/test/groovy/graphql/execution/AbortExecutionExceptionTest.groovy +++ b/src/test/groovy/graphql/execution/AbortExecutionExceptionTest.groovy @@ -36,7 +36,7 @@ class AbortExecutionExceptionTest extends Specification { @Override ErrorType getErrorType() { - return null + return ErrorType.DataFetchingException } } diff --git a/src/test/groovy/graphql/execution/DataFetcherExceptionHandlerResultTest.groovy b/src/test/groovy/graphql/execution/DataFetcherExceptionHandlerResultTest.groovy index f902efe4ab..b378785c83 100644 --- a/src/test/groovy/graphql/execution/DataFetcherExceptionHandlerResultTest.groovy +++ b/src/test/groovy/graphql/execution/DataFetcherExceptionHandlerResultTest.groovy @@ -26,7 +26,7 @@ class DataFetcherExceptionHandlerResultTest extends Specification { @Override ErrorType getErrorType() { - return null + return ErrorType.DataFetchingException } } From 91f0f5c61aab81f0f9a7a3721be8370769841f5b Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:44:48 +1100 Subject: [PATCH 78/82] Correctly set local context and context to nullable --- src/main/java/graphql/ExecutionInput.java | 4 ++-- src/main/java/graphql/TypeResolutionEnvironment.java | 4 ++-- src/main/java/graphql/execution/ExecutionContext.java | 11 +++++------ .../graphql/execution/TypeResolutionParameters.java | 8 ++++---- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main/java/graphql/ExecutionInput.java b/src/main/java/graphql/ExecutionInput.java index 669ff4ff1d..d65920fcb3 100644 --- a/src/main/java/graphql/ExecutionInput.java +++ b/src/main/java/graphql/ExecutionInput.java @@ -25,9 +25,9 @@ public class ExecutionInput { private final String query; private final String operationName; - private final Object context; + private final @Nullable Object context; private final GraphQLContext graphQLContext; - private final Object localContext; + private final @Nullable Object localContext; private final Object root; private final RawVariables rawVariables; private final Map extensions; diff --git a/src/main/java/graphql/TypeResolutionEnvironment.java b/src/main/java/graphql/TypeResolutionEnvironment.java index b3755fb90c..4a5babfcc3 100644 --- a/src/main/java/graphql/TypeResolutionEnvironment.java +++ b/src/main/java/graphql/TypeResolutionEnvironment.java @@ -28,9 +28,9 @@ public class TypeResolutionEnvironment { private final MergedField field; private final GraphQLType fieldType; private final GraphQLSchema schema; - private final Object context; + private final @Nullable Object context; private final GraphQLContext graphQLContext; - private final Object localContext; + private final @Nullable Object localContext; private final DataFetchingFieldSelectionSet fieldSelectionSet; @Internal diff --git a/src/main/java/graphql/execution/ExecutionContext.java b/src/main/java/graphql/execution/ExecutionContext.java index c6a7edc916..ac4b1a8b0d 100644 --- a/src/main/java/graphql/execution/ExecutionContext.java +++ b/src/main/java/graphql/execution/ExecutionContext.java @@ -1,6 +1,5 @@ package graphql.execution; - import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import graphql.EngineRunningState; @@ -51,9 +50,9 @@ public class ExecutionContext { private final CoercedVariables coercedVariables; private final Supplier normalizedVariables; private final Object root; - private final Object context; + private final @Nullable Object context; private final GraphQLContext graphQLContext; - private final Object localContext; + private final @Nullable Object localContext; private final Instrumentation instrumentation; private final AtomicReference> errors = new AtomicReference<>(ImmutableKit.emptyList()); private final LockKit.ReentrantLock errorsLock = new LockKit.ReentrantLock(); @@ -157,8 +156,8 @@ public Supplier getNormalizedVariables() { * @deprecated use {@link #getGraphQLContext()} instead */ @Deprecated(since = "2021-07-05") - @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) - public T getContext() { + @SuppressWarnings({ "unchecked", "TypeParameterUnusedInFormals" }) + public @Nullable T getContext() { return (T) context; } @@ -167,7 +166,7 @@ public GraphQLContext getGraphQLContext() { } @SuppressWarnings("unchecked") - public T getLocalContext() { + public @Nullable T getLocalContext() { return (T) localContext; } diff --git a/src/main/java/graphql/execution/TypeResolutionParameters.java b/src/main/java/graphql/execution/TypeResolutionParameters.java index fa99f449dd..7161a514d8 100644 --- a/src/main/java/graphql/execution/TypeResolutionParameters.java +++ b/src/main/java/graphql/execution/TypeResolutionParameters.java @@ -27,8 +27,8 @@ public class TypeResolutionParameters { private final @Nullable Object value; private final Supplier> argumentValues; private final GraphQLSchema schema; - private final Object context; - private final Object localContext; + private final @Nullable Object context; + private final @Nullable Object localContext; private final GraphQLContext graphQLContext; private final DataFetchingFieldSelectionSet selectionSet; @@ -78,7 +78,7 @@ public static Builder newParameters() { * @deprecated use {@link #getGraphQLContext()} instead */ @Deprecated(since = "2021-07-05") - public Object getContext() { + public @Nullable Object getContext() { return context; } @@ -86,7 +86,7 @@ public GraphQLContext getGraphQLContext() { return graphQLContext; } - public Object getLocalContext() { + public @Nullable Object getLocalContext() { return localContext; } From 4a05a72c22fe71d7fe0aced5d66ab9eb8ba7997a Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 26 Jan 2026 14:10:59 +1100 Subject: [PATCH 79/82] Add mocks for i18n to accommodate non-nullable validation errors --- .../validation/rules/ArgumentsOfCorrectTypeTest.groovy | 1 + .../graphql/validation/rules/DeferDirectiveLabelTest.groovy | 1 + .../graphql/validation/rules/FieldsOnCorrectTypeTest.groovy | 4 ++++ .../validation/rules/FragmentsOnCompositeTypeTest.groovy | 4 ++++ .../graphql/validation/rules/KnownArgumentNamesTest.groovy | 4 ++++ .../graphql/validation/rules/KnownDirectivesTest.groovy | 1 + .../graphql/validation/rules/KnownFragmentNamesTest.groovy | 4 ++++ .../groovy/graphql/validation/rules/KnownTypeNamesTest.groovy | 4 ++++ .../graphql/validation/rules/NoUnusedFragmentsTest.groovy | 1 + .../groovy/graphql/validation/rules/ScalarLeavesTest.groovy | 4 ++++ .../rules/VariableDefaultValuesOfCorrectTypeTest.groovy | 1 + .../validation/rules/VariablesAreInputTypesTest.groovy | 4 ++++ 12 files changed, 33 insertions(+) diff --git a/src/test/groovy/graphql/validation/rules/ArgumentsOfCorrectTypeTest.groovy b/src/test/groovy/graphql/validation/rules/ArgumentsOfCorrectTypeTest.groovy index 7e8210955e..762a5788cb 100644 --- a/src/test/groovy/graphql/validation/rules/ArgumentsOfCorrectTypeTest.groovy +++ b/src/test/groovy/graphql/validation/rules/ArgumentsOfCorrectTypeTest.groovy @@ -41,6 +41,7 @@ class ArgumentsOfCorrectTypeTest extends Specification { def context = GraphQLContext.getDefault() validationContext.getGraphQLContext() >> context validationContext.getI18n() >> i18n + validationContext.i18n(_, _) >> "test error message" i18n.getLocale() >> Locale.ENGLISH } diff --git a/src/test/groovy/graphql/validation/rules/DeferDirectiveLabelTest.groovy b/src/test/groovy/graphql/validation/rules/DeferDirectiveLabelTest.groovy index 2e3975269d..d70ef5f207 100644 --- a/src/test/groovy/graphql/validation/rules/DeferDirectiveLabelTest.groovy +++ b/src/test/groovy/graphql/validation/rules/DeferDirectiveLabelTest.groovy @@ -30,6 +30,7 @@ class DeferDirectiveLabelTest extends Specification { ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT, true ).build(); validationContext.getTraversalContext() >> traversalContext + validationContext.i18n(_, _) >> "test error message" } def "Allow unique label directive"() { diff --git a/src/test/groovy/graphql/validation/rules/FieldsOnCorrectTypeTest.groovy b/src/test/groovy/graphql/validation/rules/FieldsOnCorrectTypeTest.groovy index 8bbb8052e3..e5a28d69b5 100644 --- a/src/test/groovy/graphql/validation/rules/FieldsOnCorrectTypeTest.groovy +++ b/src/test/groovy/graphql/validation/rules/FieldsOnCorrectTypeTest.groovy @@ -18,6 +18,10 @@ class FieldsOnCorrectTypeTest extends Specification { ValidationContext validationContext = Mock(ValidationContext) FieldsOnCorrectType fieldsOnCorrectType = new FieldsOnCorrectType(validationContext, errorCollector) + def setup() { + validationContext.i18n(_, _) >> "test error message" + } + def "should add error to collector when field definition is null"() { given: diff --git a/src/test/groovy/graphql/validation/rules/FragmentsOnCompositeTypeTest.groovy b/src/test/groovy/graphql/validation/rules/FragmentsOnCompositeTypeTest.groovy index ccd918cbdb..d648973849 100644 --- a/src/test/groovy/graphql/validation/rules/FragmentsOnCompositeTypeTest.groovy +++ b/src/test/groovy/graphql/validation/rules/FragmentsOnCompositeTypeTest.groovy @@ -19,6 +19,10 @@ class FragmentsOnCompositeTypeTest extends Specification { ValidationErrorCollector errorCollector = new ValidationErrorCollector() FragmentsOnCompositeType fragmentsOnCompositeType = new FragmentsOnCompositeType(validationContext, errorCollector) + def setup() { + validationContext.i18n(_, _) >> "test error message" + } + def "inline fragment type condition must refer to a composite type"() { given: InlineFragment inlineFragment = InlineFragment.newInlineFragment().typeCondition(TypeName.newTypeName("String").build()).build() diff --git a/src/test/groovy/graphql/validation/rules/KnownArgumentNamesTest.groovy b/src/test/groovy/graphql/validation/rules/KnownArgumentNamesTest.groovy index e437b43eda..6d91157e18 100644 --- a/src/test/groovy/graphql/validation/rules/KnownArgumentNamesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/KnownArgumentNamesTest.groovy @@ -25,6 +25,10 @@ class KnownArgumentNamesTest extends Specification { ValidationErrorCollector errorCollector = new ValidationErrorCollector() KnownArgumentNames knownArgumentNames = new KnownArgumentNames(validationContext, errorCollector) + def setup() { + validationContext.i18n(_, _) >> "test error message" + } + def "unknown field argument"() { given: Argument argument = Argument.newArgument("unknownArg", StringValue.newStringValue("value").build()).build() diff --git a/src/test/groovy/graphql/validation/rules/KnownDirectivesTest.groovy b/src/test/groovy/graphql/validation/rules/KnownDirectivesTest.groovy index 8ac2b0d037..c2eac19865 100644 --- a/src/test/groovy/graphql/validation/rules/KnownDirectivesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/KnownDirectivesTest.groovy @@ -23,6 +23,7 @@ class KnownDirectivesTest extends Specification { def traversalContext = Mock(TraversalContext) validationContext.getSchema() >> StarWarsSchema.starWarsSchema validationContext.getTraversalContext() >> traversalContext + validationContext.i18n(_, _) >> "test error message" } diff --git a/src/test/groovy/graphql/validation/rules/KnownFragmentNamesTest.groovy b/src/test/groovy/graphql/validation/rules/KnownFragmentNamesTest.groovy index 5a56b13514..0e09a73aa9 100644 --- a/src/test/groovy/graphql/validation/rules/KnownFragmentNamesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/KnownFragmentNamesTest.groovy @@ -16,6 +16,10 @@ class KnownFragmentNamesTest extends Specification { ValidationErrorCollector errorCollector = new ValidationErrorCollector() KnownFragmentNames knownFragmentNames = new KnownFragmentNames(validationContext, errorCollector) + def setup() { + validationContext.i18n(_, _) >> "test error message" + } + def "unknown fragment reference in fragment spread"() { given: FragmentSpread fragmentSpread = FragmentSpread.newFragmentSpread("fragment").build() diff --git a/src/test/groovy/graphql/validation/rules/KnownTypeNamesTest.groovy b/src/test/groovy/graphql/validation/rules/KnownTypeNamesTest.groovy index fb84d8739d..8dc27e2a4c 100644 --- a/src/test/groovy/graphql/validation/rules/KnownTypeNamesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/KnownTypeNamesTest.groovy @@ -17,6 +17,10 @@ class KnownTypeNamesTest extends Specification { ValidationContext validationContext = Mock(ValidationContext) KnownTypeNames knownTypeNames = new KnownTypeNames(validationContext, errorCollector) + def setup() { + validationContext.i18n(_, _) >> "test error message" + } + def "unknown types is an error"() { given: knownTypeNames.validationContext.getSchema() >> StarWarsSchema.starWarsSchema diff --git a/src/test/groovy/graphql/validation/rules/NoUnusedFragmentsTest.groovy b/src/test/groovy/graphql/validation/rules/NoUnusedFragmentsTest.groovy index 3fde31a3fb..1bb7e5b635 100644 --- a/src/test/groovy/graphql/validation/rules/NoUnusedFragmentsTest.groovy +++ b/src/test/groovy/graphql/validation/rules/NoUnusedFragmentsTest.groovy @@ -22,6 +22,7 @@ class NoUnusedFragmentsTest extends Specification { def setup() { def traversalContext = Mock(TraversalContext) validationContext.getTraversalContext() >> traversalContext + validationContext.i18n(_, _) >> "test error message" } def "all fragment names are used"() { diff --git a/src/test/groovy/graphql/validation/rules/ScalarLeavesTest.groovy b/src/test/groovy/graphql/validation/rules/ScalarLeavesTest.groovy index 14934a4846..3f5d413b90 100644 --- a/src/test/groovy/graphql/validation/rules/ScalarLeavesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/ScalarLeavesTest.groovy @@ -21,6 +21,10 @@ class ScalarLeavesTest extends Specification { ValidationContext validationContext = Mock(ValidationContext) ScalarLeaves scalarLeaves = new ScalarLeaves(validationContext, errorCollector) + def setup() { + validationContext.i18n(_, _) >> "test error message" + } + def "subselection not allowed"() { given: Field field = newField("hello", SelectionSet.newSelectionSet([newField("world").build()]).build()).build() diff --git a/src/test/groovy/graphql/validation/rules/VariableDefaultValuesOfCorrectTypeTest.groovy b/src/test/groovy/graphql/validation/rules/VariableDefaultValuesOfCorrectTypeTest.groovy index 29f132d411..ad52085efa 100644 --- a/src/test/groovy/graphql/validation/rules/VariableDefaultValuesOfCorrectTypeTest.groovy +++ b/src/test/groovy/graphql/validation/rules/VariableDefaultValuesOfCorrectTypeTest.groovy @@ -25,6 +25,7 @@ class VariableDefaultValuesOfCorrectTypeTest extends Specification { def context = GraphQLContext.getDefault() validationContext.getGraphQLContext() >> context validationContext.getI18n() >> i18n + validationContext.i18n(_, _) >> "test error message" i18n.getLocale() >> Locale.ENGLISH } diff --git a/src/test/groovy/graphql/validation/rules/VariablesAreInputTypesTest.groovy b/src/test/groovy/graphql/validation/rules/VariablesAreInputTypesTest.groovy index b01eebabc3..f0287ab40b 100644 --- a/src/test/groovy/graphql/validation/rules/VariablesAreInputTypesTest.groovy +++ b/src/test/groovy/graphql/validation/rules/VariablesAreInputTypesTest.groovy @@ -18,6 +18,10 @@ class VariablesAreInputTypesTest extends Specification { ValidationErrorCollector errorCollector = new ValidationErrorCollector() VariablesAreInputTypes variablesAreInputTypes = new VariablesAreInputTypes(validationContext, errorCollector) + def setup() { + validationContext.i18n(_, _) >> "test error message" + } + def "the unmodified ast type is not a schema input type"() { given: def astType = new NonNullType(new ListType(new TypeName(StarWarsSchema.droidType.getName()))) From b00dafe415a24080c921ac3cb6cd4d5656e11f34 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Mon, 26 Jan 2026 14:26:14 +1100 Subject: [PATCH 80/82] Add another i18n mock --- .../validation/rules/ProvidedNonNullArgumentsTest.groovy | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/groovy/graphql/validation/rules/ProvidedNonNullArgumentsTest.groovy b/src/test/groovy/graphql/validation/rules/ProvidedNonNullArgumentsTest.groovy index 9a11066973..859e71c858 100644 --- a/src/test/groovy/graphql/validation/rules/ProvidedNonNullArgumentsTest.groovy +++ b/src/test/groovy/graphql/validation/rules/ProvidedNonNullArgumentsTest.groovy @@ -27,6 +27,10 @@ class ProvidedNonNullArgumentsTest extends Specification { ValidationErrorCollector errorCollector = new ValidationErrorCollector() ProvidedNonNullArguments providedNonNullArguments = new ProvidedNonNullArguments(validationContext, errorCollector) + def setup() { + validationContext.i18n(_, _) >> "test error message" + } + def "not provided and not defaulted non null field argument"() { given: def fieldArg = GraphQLArgument.newArgument().name("arg") From 0a4d5d01fdd58b2a77b9436c414901c42f16ecb6 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:00:43 +1100 Subject: [PATCH 81/82] Add more nullable annotations --- src/main/java/graphql/schema/GraphQLSchema.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/graphql/schema/GraphQLSchema.java b/src/main/java/graphql/schema/GraphQLSchema.java index 991fa1c9ca..705b95305c 100644 --- a/src/main/java/graphql/schema/GraphQLSchema.java +++ b/src/main/java/graphql/schema/GraphQLSchema.java @@ -55,8 +55,8 @@ public class GraphQLSchema { private final GraphQLObjectType queryType; - private final GraphQLObjectType mutationType; - private final GraphQLObjectType subscriptionType; + private final @Nullable GraphQLObjectType mutationType; + private final @Nullable GraphQLObjectType subscriptionType; private final GraphQLObjectType introspectionSchemaType; private final ImmutableSet additionalTypes; private final GraphQLFieldDefinition introspectionSchemaField; @@ -66,9 +66,9 @@ public class GraphQLSchema { private final DirectivesUtil.DirectivesHolder directiveDefinitionsHolder; private final DirectivesUtil.DirectivesHolder schemaAppliedDirectivesHolder; - private final SchemaDefinition definition; + private final @Nullable SchemaDefinition definition; private final ImmutableList extensionDefinitions; - private final String description; + private final @Nullable String description; private final @Nullable GraphQLCodeRegistry codeRegistry; private final ImmutableMap typeMap; @@ -530,14 +530,14 @@ public GraphQLObjectType getQueryType() { /** * @return the Mutation type of the schema of null if there is not one */ - public GraphQLObjectType getMutationType() { + public @Nullable GraphQLObjectType getMutationType() { return mutationType; } /** * @return the Subscription type of the schema of null if there is not one */ - public GraphQLObjectType getSubscriptionType() { + public @Nullable GraphQLObjectType getSubscriptionType() { return subscriptionType; } From d83f175c46546062b1d049a68efc3244b2dfa155 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 8 Feb 2026 18:02:01 +1100 Subject: [PATCH 82/82] Add assert --- src/main/java/graphql/schema/GraphQLSchema.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/graphql/schema/GraphQLSchema.java b/src/main/java/graphql/schema/GraphQLSchema.java index 705b95305c..bf4f5098ec 100644 --- a/src/main/java/graphql/schema/GraphQLSchema.java +++ b/src/main/java/graphql/schema/GraphQLSchema.java @@ -184,7 +184,7 @@ private GraphQLSchema(FastBuilder fastBuilder) { ImmutableMap.Builder> interfaceMapBuilder = ImmutableMap.builder(); for (Map.Entry> entry : finalInterfaceNameMap.entrySet()) { ImmutableList objectTypes = map(entry.getValue(), - name -> (GraphQLObjectType) finalTypeMap.get(name)); + name -> (GraphQLObjectType) assertNotNull(finalTypeMap.get(name))); interfaceMapBuilder.put(entry.getKey(), objectTypes); } ImmutableMap> finalInterfaceMap = interfaceMapBuilder.build();