Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
1b01d2b
Annotate GraphQLAppliedDirective
dondonz Aug 21, 2025
8684b48
Add GraphQLSchema annotation
dondonz Aug 21, 2025
0cdc8de
Remove exemption
dondonz Aug 21, 2025
30d0abe
Annotate GraphQLType and GraphQLEnumType
dondonz Aug 21, 2025
6ee01b8
Add Nullmarked to GraphQLEnumType
dondonz Aug 21, 2025
a45c613
Annotate GraphQLCodeRegistry
dondonz Aug 21, 2025
70336d5
Annotate GraphQLList
dondonz Aug 21, 2025
29f8363
Annotate PropertyDataFetcher
dondonz Aug 21, 2025
083b2ab
Annotate GraphQLUnionType
dondonz Aug 21, 2025
b4761ef
Tidy up
dondonz Aug 21, 2025
9a78fb3
Annotate GraphQLScalarType
dondonz Aug 21, 2025
7bbfd9c
Annotate GraphQLNamedInputType
dondonz Aug 21, 2025
7427f64
Add ErrorType
dondonz Aug 23, 2025
120095c
Annotate Directives
dondonz Aug 23, 2025
012f49c
Annotate ErrorClassification
dondonz Aug 23, 2025
afdd408
Annotate ExceptionWhileDataFetching
dondonz Aug 23, 2025
803c924
Annotate ExecutionResult
dondonz Aug 27, 2025
a322ed0
Annotate GraphQLContext
dondonz Aug 27, 2025
41320d4
Annotate AssertException and GraphQLError
dondonz Aug 28, 2025
9765c21
Annotate GraphQLErrorBuilder
dondonz Aug 28, 2025
0ee713c
Annotate GraphQLErrorException
dondonz Aug 28, 2025
e54fca2
Annotate ParseAndValidate
dondonz Aug 28, 2025
603c00d
Annotate ParseAndValidateResult
dondonz Aug 28, 2025
9fcaea1
Annotate Scalars
dondonz Aug 28, 2025
88b02f3
Annotate SerializationError
dondonz Aug 28, 2025
e65217c
Update GraphQLError with a nullable location list
dondonz Aug 28, 2025
19000a5
Annotate TypeMismatchError
dondonz Aug 28, 2025
313bf4f
Annotate UnresolvedTypeError
dondonz Aug 28, 2025
0846c55
Remove agent as no longer exists
dondonz Aug 30, 2025
d2d87cd
Add JSpecify prompt
dondonz Aug 30, 2025
f4b05bb
Fix NullAway issues with assertions
dondonz Aug 30, 2025
ab1ce8d
Remove null checks
dondonz Aug 30, 2025
241b7a0
Annotate DefaultConnection
dondonz Aug 30, 2025
7ffd1ba
Annotate Relay Connection
dondonz Aug 30, 2025
003958f
Update URL
dondonz Aug 30, 2025
ba3de3e
Annotate DefaultConnectionCursor
dondonz Aug 30, 2025
8de5a75
Annotate ConnectionCursor
dondonz Aug 30, 2025
0bee1f8
Annotate DefaultEdge
dondonz Aug 30, 2025
a6e7382
Annotate DefaultPageInfo
dondonz Aug 30, 2025
e508453
Annotate Edge
dondonz Aug 30, 2025
ee76574
Annotate PageInfo
dondonz Aug 30, 2025
b7a7be6
Annotate SimpleListConnection
dondonz Aug 30, 2025
81beb0d
Annotate Relay
dondonz Aug 30, 2025
9c5e4c8
Annotate ValidationErrorClassification and ValidationErrorType
dondonz Aug 30, 2025
6ec7d82
Annotate ValidationError
dondonz Aug 30, 2025
98ff640
Annotate NamedNode
dondonz Aug 30, 2025
3f58265
Annotate Argument
dondonz Aug 30, 2025
eb98025
Annotate GraphQLTypeUtil
dondonz Aug 30, 2025
97b407d
Fix typo
dondonz Aug 30, 2025
44253f7
Update prompt
dondonz Aug 30, 2025
e8b1670
Annotate Breadcrumb
dondonz Aug 30, 2025
f1b5529
Annotate FieldComplexityCalculator
dondonz Aug 30, 2025
230b757
Annotate FieldComplexityEnvironment
dondonz Aug 30, 2025
ff81ff6
Annotate TypeResolutionEnvironment
dondonz Aug 30, 2025
ed2961f
Annotate MaxQueryComplexityInstrumentation
dondonz Aug 30, 2025
cf724bf
Annotate MaxQueryDepthInstrumentation
dondonz Aug 30, 2025
8a47304
Make ValidationError description nullable as message is nullable
dondonz Aug 31, 2025
de6ad66
Add Nullmarked
dondonz Aug 31, 2025
fd3b4af
Revert to nullable description and type for ValidationError
dondonz Aug 31, 2025
71ef680
Update test for Argument, and make nonnullable field clearer
dondonz Aug 31, 2025
23afaea
Annotate Anonymizer
dondonz Sep 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .claude/commands/jspecify-annotate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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.

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
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.

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

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.
3 changes: 3 additions & 0 deletions src/main/java/graphql/AssertException.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package graphql;


import org.jspecify.annotations.NullMarked;

@PublicApi
@NullMarked
public class AssertException extends GraphQLException {

public AssertException(String message) {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/graphql/Directives.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -34,6 +35,7 @@
* The directives that are understood by graphql-java
*/
@PublicApi
@NullMarked
public class Directives {

private static final String DEPRECATED = "deprecated";
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/graphql/ErrorClassification.java
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -8,6 +10,7 @@
* graphql-java ships with a standard set of error classifications via {@link graphql.ErrorType}
*/
@PublicApi
@NullMarked
public interface ErrorClassification {

/**
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/graphql/ErrorType.java
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
9 changes: 6 additions & 3 deletions src/main/java/graphql/ExceptionWhileDataFetching.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Object> path;
private final Throwable exception;
private final List<SourceLocation> locations;
private final Map<String, Object> extensions;
private final @Nullable Map<String, Object> extensions;

public ExceptionWhileDataFetching(ResultPath path, Throwable exception, SourceLocation sourceLocation) {
this.path = assertNotNull(path).toList();
Expand All @@ -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<String, Object> mkExtensions(Throwable exception) {
private @Nullable Map<String, Object> mkExtensions(Throwable exception) {
Map<String, Object> extensions = null;
if (exception instanceof GraphQLError) {
Map<String, Object> map = ((GraphQLError) exception).getExtensions();
Expand Down Expand Up @@ -73,7 +76,7 @@ public List<Object> getPath() {
}

@Override
public Map<String, Object> getExtensions() {
public @Nullable Map<String, Object> getExtensions() {
return extensions;
}

Expand Down
18 changes: 12 additions & 6 deletions src/main/java/graphql/ExecutionResult.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,6 +13,7 @@
* This simple value class represents the result of performing a graphql query.
*/
@PublicApi
@NullMarked
@SuppressWarnings("TypeParameterUnusedInFormals")
public interface ExecutionResult {

Expand All @@ -22,16 +27,16 @@ public interface ExecutionResult {
*
* @return the data in the result or null if there is none
*/
<T> T getData();
<T> @Nullable T getData();

/**
* The graphql specification specifies:
*
* <p>
* "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."
*
* <p>
* This allows to distinguish between the cases where {@link #getData()} returns null.
*
* <p>
* See : <a href="https://graphql.github.io/graphql-spec/June2018/#sec-Data">https://graphql.github.io/graphql-spec/June2018/#sec-Data</a>
*
* @return <code>true</code> if the entry "data" should be present in the result
Expand All @@ -42,14 +47,14 @@ public interface ExecutionResult {
/**
* @return a map of extensions or null if there are none
*/
Map<Object, Object> getExtensions();
@Nullable Map<Object, Object> 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.
*
* <p>
* See : <a href="https://spec.graphql.org/October2021/#sec-Response-Format">https://spec.graphql.org/October2021/#sec-Response-Format</a>
*
* @return a map of the result that strictly follows the spec
Expand Down Expand Up @@ -88,6 +93,7 @@ static Builder<?> newExecutionResult() {
return ExecutionResultImpl.newExecutionResult();
}

@NullUnmarked
interface Builder<B extends Builder<B>> {

/**
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/graphql/GraphQL.java
Original file line number Diff line number Diff line change
Expand Up @@ -559,14 +559,14 @@ private PreparsedDocumentEntry parseAndValidate(AtomicReference<ExecutionInput>

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<ValidationError> errors = validate(executionInput, document, graphQLSchema, instrumentationState);
final List<ValidationError> errors = validate(executionInput, assertNotNull(document, () -> "Document cannot be null when parse succeeded"), graphQLSchema, instrumentationState);
if (!errors.isEmpty()) {
return new PreparsedDocumentEntry(document, errors);
}
Expand Down Expand Up @@ -599,7 +599,7 @@ private List<ValidationError> validate(ExecutionInput executionInput, Document d
validationCtx.onDispatched();

Predicate<Class<?>> 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<ValidationError> validationErrors = ParseAndValidate.validate(graphQLSchema, document, validationRulePredicate, locale);

validationCtx.onCompleted(validationErrors, null);
Expand Down
18 changes: 12 additions & 6 deletions src/main/java/graphql/GraphQLContext.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -32,12 +36,13 @@
* You can set this up via {@link ExecutionInput#getGraphQLContext()}
*
* All keys and values in the context MUST be non null.
*
* <p>
* 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<Object, Object> map;
Expand Down Expand Up @@ -66,7 +71,7 @@ public GraphQLContext delete(Object key) {
*
* @return a value or null
*/
public <T> T get(Object key) {
public <T> @Nullable T get(Object key) {
return (T) map.get(assertNotNull(key));
}

Expand Down Expand Up @@ -210,7 +215,7 @@ public GraphQLContext putAll(Consumer<GraphQLContext.Builder> contextBuilderCons
*
* @return the new value associated with the specified key, or null if none
*/
public <T> T compute(Object key, BiFunction<Object, ? super T, ? extends T> remappingFunction) {
public <T> @Nullable T compute(Object key, BiFunction<Object, ? super T, ? extends T> remappingFunction) {
assertNotNull(remappingFunction);
return (T) map.compute(assertNotNull(key), (k, v) -> remappingFunction.apply(k, (T) v));
}
Expand All @@ -226,7 +231,7 @@ public <T> T compute(Object key, BiFunction<Object, ? super T, ? extends T> rema
* @return the current (existing or computed) value associated with the specified key, or null if the computed value is null
*/

public <T> T computeIfAbsent(Object key, Function<Object, ? extends T> mappingFunction) {
public <T> @Nullable T computeIfAbsent(Object key, Function<Object, ? extends T> mappingFunction) {
return (T) map.computeIfAbsent(assertNotNull(key), assertNotNull(mappingFunction));
}

Expand All @@ -241,7 +246,7 @@ public <T> T computeIfAbsent(Object key, Function<Object, ? extends T> mappingFu
* @return the new value associated with the specified key, or null if none
*/

public <T> T computeIfPresent(Object key, BiFunction<Object, ? super T, ? extends T> remappingFunction) {
public <T> @Nullable T computeIfPresent(Object key, BiFunction<Object, ? super T, ? extends T> remappingFunction) {
assertNotNull(remappingFunction);
return (T) map.computeIfPresent(assertNotNull(key), (k, v) -> remappingFunction.apply(k, (T) v));
}
Expand All @@ -254,7 +259,7 @@ public Stream<Map.Entry<Object, Object>> stream() {
}

@Override
public boolean equals(Object o) {
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
Expand Down Expand Up @@ -315,6 +320,7 @@ public static Builder newContext() {
return new Builder();
}

@NullUnmarked
public static class Builder {
private final ConcurrentMap<Object, Object> map = new ConcurrentHashMap<>();

Expand Down
Loading