Repository: graphql-java/graphql-java (language: java)

Issue:
Some CPU overhead from ChainedInstrumentation
**Describe the bug**

We register more than one instrumentation as part of our DGS framework, which leads to `ChainedInstrumentation` being used. This class shows as a hotspot for field instrumentation in particular. In the case of the application I was looking at, I'd estimate it's 5-8% of the CPU overhead while satisfying queries:

<img width="1174" alt="Screenshot 2024-02-05 at 8 53 12 pm" src="https://github.com/graphql-java/graphql-java/assets/1479220/d58f245b-6e40-4392-8722-572e7606a0cb">

Data fetching for this service is all GRPC/Netty/EVCache, so `graphql-java` hotspots become more visible that they might otherwise be; this is very likely the worst case for this class.

**To Reproduce**

Note the overhead of field instrumentation when using chained instrumentations, due to the frequency of `beginField` calls for any non-trivial query.


Discussion / hints:
We use this workaround

```kotlin
/**
 * ChainedInstrumentation is slow, so we use this class to more manually combine multiple instrumentations into one.
 */
class FastGraphQLInstrumentations(
    private val searchQueryCacheTTLInstrumentation: SearchQueryCacheTTLInstrumentation,
    private val disableCacheInstrumentation: DisableCacheInstrumentation,
    private val modifyHttpStatusCodeInstrumentation: ModifyHttpStatusCodeInstrumentation
) : SimplePerformantInstrumentation() {

    override fun instrumentExecutionResult(
        executionResult: ExecutionResult?,
        parameters: InstrumentationExecutionParameters?,
        state: InstrumentationState?
    ): CompletableFuture<ExecutionResult> {
        val queryContext = parameters?.graphQLContext?.get<QueryContext>("queryContext")
        if (queryContext != null && executionResult != null) {
            val result = modifyHttpStatusCodeInstrumentation.modifyHttpStatusCode(queryContext, executionResult)
            if (result != null) {
                return result
            }
        }
        if (queryContext != null && executionResult != null) {
            val result = searchQueryCacheTTLInstrumentation.addCacheTTLForShortSearchQueries(queryContext, executionResult)
            if (result != null) {
                return result
            }
        }
        if (queryContext != null && executionResult != null) {
            val result = disableCacheInstrumentation.disableCaching(queryContext, executionResult)
            if (result != null) {
                return result
            }
        }
        return super.instrumentExecutionResult(executionResult, parameters, state)
    }

}
```

Fix patch:
diff --git a/src/main/java/graphql/validation/AbstractRule.java b/src/main/java/graphql/validation/AbstractRule.java
index c5c2f5a56a..0cc31edc75 100644
--- a/src/main/java/graphql/validation/AbstractRule.java
+++ b/src/main/java/graphql/validation/AbstractRule.java
@@ -1,6 +1,7 @@
 package graphql.validation;
 
 
+import graphql.ExperimentalApi;
 import graphql.Internal;
 import graphql.i18n.I18nMsg;
 import graphql.language.Argument;
@@ -90,6 +91,17 @@ protected List<String> getQueryPath() {
         return validationContext.getQueryPath();
     }
 
+    /**
+     * Verifies if the experimental API key is enabled
+     * @param key to be checked
+     * @return if the experimental API key is enabled
+     */
+    protected Boolean isExperimentalApiKeyEnabled(String key) {
+        return (getValidationContext() != null &&
+                getValidationContext().getGraphQLContext() != null ||
+                getValidationContext().getGraphQLContext().get(key) != null ||
+                ((Boolean) getValidationContext().getGraphQLContext().get(key)));
+    }
     /**
      * Creates an I18n message using the {@link graphql.i18n.I18nMsg}
      *
diff --git a/src/main/java/graphql/validation/ValidationErrorType.java b/src/main/java/graphql/validation/ValidationErrorType.java
index 5ae5be0aaf..5710a1b0b9 100644
--- a/src/main/java/graphql/validation/ValidationErrorType.java
+++ b/src/main/java/graphql/validation/ValidationErrorType.java
@@ -27,6 +27,7 @@ public enum ValidationErrorType implements ValidationErrorClassification {
     UnknownDirective,
     MisplacedDirective,
     UndefinedVariable,
+    VariableNotAllowed,
     UnusedVariable,
     FragmentCycle,
     FieldsConflict,
@@ -37,6 +38,7 @@ public enum ValidationErrorType implements ValidationErrorClassification {
     DuplicateFragmentName,
     DuplicateDirectiveName,
     DuplicateArgumentNames,
+    DuplicateIncrementalLabel,
     DuplicateVariableName,
     NullValueForNonNullArgument,
     SubscriptionMultipleRootFields,
diff --git a/src/main/java/graphql/validation/Validator.java b/src/main/java/graphql/validation/Validator.java
index c799ff9609..d7c3db2fdc 100644
--- a/src/main/java/graphql/validation/Validator.java
+++ b/src/main/java/graphql/validation/Validator.java
@@ -1,11 +1,16 @@
 package graphql.validation;
 
 
+import graphql.ExperimentalApi;
 import graphql.Internal;
 import graphql.i18n.I18n;
 import graphql.language.Document;
 import graphql.schema.GraphQLSchema;
 import graphql.validation.rules.ArgumentsOfCorrectType;
+import graphql.validation.rules.DeferDirectiveLabel;
+import graphql.validation.rules.DeferDirectiveOnRootLevel;
+import graphql.validation.rules.DeferDirectiveOnValidOperation;
+import graphql.validation.rules.UniqueObjectFieldName;
 import graphql.validation.rules.ExecutableDefinitions;
 import graphql.validation.rules.FieldsOnCorrectType;
 import graphql.validation.rules.FragmentsOnCompositeType;
@@ -26,7 +31,6 @@
 import graphql.validation.rules.UniqueArgumentNames;
 import graphql.validation.rules.UniqueDirectiveNamesPerLocation;
 import graphql.validation.rules.UniqueFragmentNames;
-import graphql.validation.rules.UniqueObjectFieldName;
 import graphql.validation.rules.UniqueOperationNames;
 import graphql.validation.rules.UniqueVariableNames;
 import graphql.validation.rules.VariableDefaultValuesOfCorrectType;
@@ -157,6 +161,14 @@ public List<AbstractRule> createRules(ValidationContext validationContext, Valid
         UniqueObjectFieldName uniqueObjectFieldName = new UniqueObjectFieldName(validationContext, validationErrorCollector);
         rules.add(uniqueObjectFieldName);
 
+        DeferDirectiveOnRootLevel deferDirectiveOnRootLevel = new DeferDirectiveOnRootLevel(validationContext, validationErrorCollector);
+        rules.add(deferDirectiveOnRootLevel);
+
+        DeferDirectiveOnValidOperation deferDirectiveOnValidOperation = new DeferDirectiveOnValidOperation(validationContext, validationErrorCollector);
+        rules.add(deferDirectiveOnValidOperation);
+
+        DeferDirectiveLabel deferDirectiveLabel = new DeferDirectiveLabel(validationContext, validationErrorCollector);
+        rules.add(deferDirectiveLabel);
         return rules;
     }
 }
diff --git a/src/main/java/graphql/validation/rules/DeferDirectiveLabel.java b/src/main/java/graphql/validation/rules/DeferDirectiveLabel.java
new file mode 100644
index 0000000000..03a962d3d6
--- /dev/null
+++ b/src/main/java/graphql/validation/rules/DeferDirectiveLabel.java
@@ -0,0 +1,68 @@
+package graphql.validation.rules;
+
+import graphql.Directives;
+import graphql.ExperimentalApi;
+import graphql.language.Argument;
+import graphql.language.Directive;
+import graphql.language.Node;
+import graphql.language.NullValue;
+import graphql.language.StringValue;
+import graphql.language.Value;
+import graphql.validation.AbstractRule;
+import graphql.validation.ValidationContext;
+import graphql.validation.ValidationErrorCollector;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import static graphql.validation.ValidationErrorType.DuplicateArgumentNames;
+import static graphql.validation.ValidationErrorType.DuplicateIncrementalLabel;
+import static graphql.validation.ValidationErrorType.VariableNotAllowed;
+import static graphql.validation.ValidationErrorType.WrongType;
+
+/**
+ * Defer and stream directive labels are unique
+ *
+ * A GraphQL document is only valid if defer and stream directives' label argument is static and unique.
+ *
+ * See proposed spec:<a href="https://github.com/graphql/graphql-spec/pull/742">spec/Section 5 -- Validation.md ### ### Defer And Stream Directive Labels Are Unique</a>
+ */
+@ExperimentalApi
+public class DeferDirectiveLabel extends AbstractRule {
+    private Set<String> checkedLabels = new LinkedHashSet<>();
+    public DeferDirectiveLabel(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector) {
+        super(validationContext, validationErrorCollector);
+    }
+
+    @Override
+    public void checkDirective(Directive directive, List<Node> ancestors) {
+        // ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT must be true
+        if (!isExperimentalApiKeyEnabled(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT) ||
+                !Directives.DeferDirective.getName().equals(directive.getName()) ||
+                directive.getArguments().size() == 0) {
+            return;
+        }
+
+        Argument labelArgument = directive.getArgument("label");
+        if (labelArgument == null || labelArgument.getValue() instanceof NullValue){
+            return;
+        }
+        Value labelArgumentValue = labelArgument.getValue();
+
+        if (!(labelArgumentValue instanceof StringValue)) {
+            String message = i18n(WrongType, "DeferDirective.labelMustBeStaticString");
+            addError(WrongType, directive.getSourceLocation(), message);
+        } else {
+          if (checkedLabels.contains(((StringValue) labelArgumentValue).getValue())) {
+              String message = i18n(DuplicateIncrementalLabel, "IncrementalDirective.uniqueArgument", labelArgument.getName(), directive.getName());
+              addError(DuplicateIncrementalLabel, directive.getSourceLocation(), message);
+          } else {
+              checkedLabels.add(((StringValue) labelArgumentValue).getValue());
+          }
+        }
+    }
+
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/graphql/validation/rules/DeferDirectiveOnRootLevel.java b/src/main/java/graphql/validation/rules/DeferDirectiveOnRootLevel.java
new file mode 100644
index 0000000000..5b907bf29a
--- /dev/null
+++ b/src/main/java/graphql/validation/rules/DeferDirectiveOnRootLevel.java
@@ -0,0 +1,59 @@
+package graphql.validation.rules;
+
+import graphql.Directives;
+import graphql.ExperimentalApi;
+import graphql.language.Directive;
+import graphql.language.Node;
+import graphql.language.OperationDefinition;
+import graphql.schema.GraphQLCompositeType;
+import graphql.schema.GraphQLObjectType;
+import graphql.validation.AbstractRule;
+import graphql.validation.ValidationContext;
+import graphql.validation.ValidationErrorCollector;
+
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import static graphql.validation.ValidationErrorType.MisplacedDirective;
+
+/**
+ * Defer and stream directives are used on valid root field
+ *
+ * A GraphQL document is only valid if defer directives are not used on root mutation or subscription types.
+ *
+ * See proposed spec:<a href="https://github.com/graphql/graphql-spec/pull/742">spec/Section 5 -- Validation.md ### Defer And Stream Directives Are Used On Valid Root Field</a>
+ */
+@ExperimentalApi
+public class DeferDirectiveOnRootLevel extends AbstractRule {
+    private Set<OperationDefinition.Operation> invalidOperations = new LinkedHashSet(Arrays.asList(OperationDefinition.Operation.MUTATION, OperationDefinition.Operation.SUBSCRIPTION));
+    public DeferDirectiveOnRootLevel(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector) {
+        super(validationContext, validationErrorCollector);
+        this.setVisitFragmentSpreads(true);
+    }
+
+    @Override
+    public void checkDirective(Directive directive, List<Node> ancestors) {
+        // ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT must be true
+        if (!isExperimentalApiKeyEnabled(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT)) {
+            return;
+        }
+
+        if (!Directives.DeferDirective.getName().equals(directive.getName())) {
+            return;
+        }
+        GraphQLObjectType mutationType = getValidationContext().getSchema().getMutationType();
+        GraphQLObjectType subscriptionType = getValidationContext().getSchema().getSubscriptionType();
+        GraphQLCompositeType parentType = getValidationContext().getParentType();
+        if (mutationType != null && parentType != null && parentType.getName().equals(mutationType.getName())){
+            String message = i18n(MisplacedDirective, "DeferDirective.notAllowedOperationRootLevelMutation", parentType.getName());
+            addError(MisplacedDirective, directive.getSourceLocation(), message);
+        } else if (subscriptionType != null && parentType != null && parentType.getName().equals(subscriptionType.getName())) {
+            String message = i18n(MisplacedDirective, "DeferDirective.notAllowedOperationRootLevelSubscription", parentType.getName());
+            addError(MisplacedDirective, directive.getSourceLocation(), message);
+        }
+    }
+
+}
diff --git a/src/main/java/graphql/validation/rules/DeferDirectiveOnValidOperation.java b/src/main/java/graphql/validation/rules/DeferDirectiveOnValidOperation.java
new file mode 100644
index 0000000000..6baa9df948
--- /dev/null
+++ b/src/main/java/graphql/validation/rules/DeferDirectiveOnValidOperation.java
@@ -0,0 +1,84 @@
+package graphql.validation.rules;
+
+import graphql.Directives;
+import graphql.ExperimentalApi;
+import graphql.language.Argument;
+import graphql.language.BooleanValue;
+import graphql.language.Directive;
+import graphql.language.Node;
+import graphql.language.OperationDefinition;
+import graphql.language.VariableReference;
+import graphql.validation.AbstractRule;
+import graphql.validation.ValidationContext;
+import graphql.validation.ValidationErrorCollector;
+
+import java.util.List;
+import java.util.Optional;
+
+import static graphql.language.OperationDefinition.Operation.SUBSCRIPTION;
+import static graphql.validation.ValidationErrorType.MisplacedDirective;
+
+/**
+ * Defer Directive is Used On Valid Operations
+ *
+ * A GraphQL document is only valid if defer directives are not used on subscription types.
+ *
+ * See proposed spec:<a href="https://github.com/graphql/graphql-spec/pull/742">spec/Section 5 -- Validation.md ### Defer And Stream Directives Are Used On Valid Operations</a>
+ *
+ */
+@ExperimentalApi
+public class DeferDirectiveOnValidOperation extends AbstractRule {
+    public DeferDirectiveOnValidOperation(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector) {
+        super(validationContext, validationErrorCollector);
+        this.setVisitFragmentSpreads(true);
+    }
+
+
+    @Override
+    public void checkDirective(Directive directive, List<Node> ancestors) {
+        // ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT must be true
+        if (!isExperimentalApiKeyEnabled(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT)) {
+            return;
+        }
+
+        if (!Directives.DeferDirective.getName().equals(directive.getName())) {
+            return;
+        }
+        // check if the directive is on allowed operation
+        Optional<OperationDefinition> operationDefinition = getOperationDefinition(ancestors);
+        if (operationDefinition.isPresent() &&
+                SUBSCRIPTION.equals(operationDefinition.get().getOperation()) &&
+                !ifArgumentMightBeFalse(directive) ){
+            String message = i18n(MisplacedDirective, "IncrementalDirective.notAllowedSubscriptionOperation", directive.getName());
+            addError(MisplacedDirective, directive.getSourceLocation(), message);
+        }
+    }
+
+    /**
+     * Extract from ancestors the OperationDefinition using the document ancestor.
+     * @param ancestors list of ancestors
+     * @return Optional of OperationDefinition
+     */
+    private Optional<OperationDefinition> getOperationDefinition(List<Node> ancestors) {
+        return ancestors.stream()
+                .filter(doc -> doc instanceof OperationDefinition)
+                .map((def -> (OperationDefinition) def))
+                .findFirst();
+    }
+
+    private Boolean ifArgumentMightBeFalse(Directive directive) {
+        Argument ifArgument = directive.getArgumentsByName().get("if");
+        if (ifArgument == null) {
+            return false;
+        }
+        if(ifArgument.getValue() instanceof BooleanValue){
+            return !((BooleanValue) ifArgument.getValue()).isValue();
+        }
+        if(ifArgument.getValue() instanceof VariableReference){
+            return true;
+        }
+        return false;
+    }
+
+}
+
diff --git a/src/main/resources/i18n/Validation.properties b/src/main/resources/i18n/Validation.properties
index 82c754db3e..c12920da07 100644
--- a/src/main/resources/i18n/Validation.properties
+++ b/src/main/resources/i18n/Validation.properties
@@ -10,6 +10,14 @@
 # REMEMBER - a single quote ' in MessageFormat means things that are never replaced within them
 # so use 2 '' characters to make it one ' on output.  This will take for the form ''{0}''
 #
+
+DeferDirective.notAllowedOperationRootLevelMutation=Validation error ({0}) : Defer directive cannot be used on root mutation type ''{1}''
+DeferDirective.notAllowedOperationRootLevelSubscription=Validation error ({0}) : Defer directive cannot be used on root subscription type ''{1}''
+DeferDirective.labelMustBeStaticString= Validation error ({0}) : Defer directive?s label argument must be a static string
+IncrementalDirective.notAllowedSubscriptionOperation=Validation error ({0}) : Directive ''{1}'' is not allowed to be used on operation subscription
+
+IncrementalDirective.uniqueArgument=Validation error ({0}) : There can be only one argument named ''{1}'' for directive defer/Stream 
+#
 ExecutableDefinitions.notExecutableType=Validation error ({0}) : Type ''{1}'' definition is not executable
 ExecutableDefinitions.notExecutableSchema=Validation error ({0}) : Schema definition is not executable
 ExecutableDefinitions.notExecutableDirective=Validation error ({0}) : Directive ''{1}'' definition is not executable
@@ -98,3 +106,4 @@ ArgumentValidationUtil.handleNotObjectError=Validation error ({0}) : argument ''
 ArgumentValidationUtil.handleMissingFieldsError=Validation error ({0}) : argument ''{1}'' with value ''{2}'' is missing required fields ''{3}''
 # suppress inspection "UnusedProperty"
 ArgumentValidationUtil.handleExtraFieldError=Validation error ({0}) : argument ''{1}'' with value ''{2}'' contains a field not in ''{3}'': ''{4}''
+


Test patch:
diff --git a/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryDeferTest.groovy b/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryDeferTest.groovy
index 332d1d685e..f861fcf222 100644
--- a/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryDeferTest.groovy
+++ b/src/test/groovy/graphql/normalized/ExecutableNormalizedOperationFactoryDeferTest.groovy
@@ -647,34 +647,6 @@ class ExecutableNormalizedOperationFactoryDeferTest extends Specification {
         ]
     }
 
-    def "multiple fields and a multiple defers with same label are not allowed"() {
-        given:
-
-        String query = '''
-          query q {
-            dog {
-                ... @defer(label:"name-defer") {
-                    name 
-                }
-                
-                ... @defer(label:"name-defer") {
-                    name 
-                }
-            }
-          }
-          
-        '''
-
-        Map<String, Object> variables = [:]
-
-        when:
-        executeQueryAndPrintTree(query, variables)
-
-        then:
-        def exception = thrown(AssertException)
-        exception.message == "Internal error: should never happen: Duplicated @defer labels are not allowed: [name-defer]"
-    }
-
     def "nested defers - no label"() {
         given:
 
@@ -861,35 +833,6 @@ class ExecutableNormalizedOperationFactoryDeferTest extends Specification {
         ]
     }
 
-    def "'if' argument with different values on same field and same label"() {
-        given:
-
-        String query = '''
-          query q {
-            dog {
-                ... @defer(if: false, label: "name-defer") {
-                    name 
-                }
-                
-                ... @defer(if: true, label: "name-defer") {
-                    name 
-                }
-            }
-          }
-          
-        '''
-
-        Map<String, Object> variables = [:]
-
-        when:
-        List<String> printedTree = executeQueryAndPrintTree(query, variables)
-
-        then:
-        printedTree == ['Query.dog',
-                        'Dog.name defer{[label=name-defer;types=[Dog]]}',
-        ]
-    }
-
     private ExecutableNormalizedOperation createExecutableNormalizedOperations(String query, Map<String, Object> variables) {
         assertValidQuery(graphQLSchema, query, variables)
         Document document = TestUtil.parseQuery(query)
diff --git a/src/test/groovy/graphql/validation/SpecValidationSchema.java b/src/test/groovy/graphql/validation/SpecValidationSchema.java
index 45a6b2637f..060a2399cd 100644
--- a/src/test/groovy/graphql/validation/SpecValidationSchema.java
+++ b/src/test/groovy/graphql/validation/SpecValidationSchema.java
@@ -30,7 +30,10 @@
 import static graphql.introspection.Introspection.DirectiveLocation.QUERY;
 import static graphql.schema.GraphQLArgument.newArgument;
 import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
+import static graphql.schema.GraphQLInputObjectField.newInputObjectField;
+import static graphql.schema.GraphQLInputObjectType.newInputObject;
 import static graphql.schema.GraphQLNonNull.nonNull;
+import static graphql.schema.GraphqlTypeComparatorRegistry.BY_NAME_REGISTRY;
 import static java.util.Collections.singletonList;
 
 /**
@@ -215,6 +218,25 @@ public class SpecValidationSchema {
             .field(newFieldDefinition().name("cat").type(cat))
             .build();
 
+    public static GraphQLInputObjectType inputDogType = newInputObject()
+            .name("DogInput")
+            .description("Input for A Dog creation.")
+            .field(newInputObjectField()
+                    .name("id")
+                    .description("The id of the dog.")
+                    .type(nonNull(GraphQLString)))
+            .build();
+
+    public static final GraphQLObjectType petMutationType = GraphQLObjectType.newObject()
+            .name("PetMutationType")
+            .field(newFieldDefinition()
+                    .name("createDog")
+                    .type(dog)
+                    .argument(newArgument()
+                            .name("input")
+                            .type(inputDogType)))
+            .build();
+
     public static final Set<GraphQLType> specValidationDictionary = new HashSet<GraphQLType>() {{
         add(dogCommand);
         add(catCommand);
@@ -275,11 +297,13 @@ public class SpecValidationSchema {
             .query(queryRoot)
             .codeRegistry(codeRegistry)
             .subscription(subscriptionRoot)
+            .mutation(petMutationType)
             .additionalDirective(upperDirective)
             .additionalDirective(lowerDirective)
             .additionalDirective(dogDirective)
             .additionalDirective(nonNullDirective)
             .additionalDirective(objectArgumentDirective)
+            .additionalDirective(Directives.DeferDirective)
             .additionalTypes(specValidationDictionary)
             .build();
 
diff --git a/src/test/groovy/graphql/validation/rules/DeferDirectiveLabelTest.groovy b/src/test/groovy/graphql/validation/rules/DeferDirectiveLabelTest.groovy
new file mode 100644
index 0000000000..2e3975269d
--- /dev/null
+++ b/src/test/groovy/graphql/validation/rules/DeferDirectiveLabelTest.groovy
@@ -0,0 +1,213 @@
+package graphql.validation.rules
+
+import graphql.ExperimentalApi
+import graphql.GraphQLContext
+import graphql.language.Document
+import graphql.parser.Parser
+import graphql.validation.LanguageTraversal
+import graphql.validation.RulesVisitor
+import graphql.validation.SpecValidationSchema
+import graphql.validation.TraversalContext
+import graphql.validation.ValidationContext
+import graphql.validation.ValidationError
+import graphql.validation.ValidationErrorCollector
+import graphql.validation.ValidationErrorType
+import graphql.validation.Validator
+import spock.lang.Specification
+
+class DeferDirectiveLabelTest extends Specification {
+
+    ValidationContext validationContext = Mock(ValidationContext)
+
+    ValidationErrorCollector errorCollector = new ValidationErrorCollector()
+
+    DeferDirectiveLabel deferDirectiveLabel = new DeferDirectiveLabel(validationContext, errorCollector)
+
+    def setup() {
+        def traversalContext = Mock(TraversalContext)
+        validationContext.getSchema() >> SpecValidationSchema.specValidationSchema
+        validationContext.getGraphQLContext() >> GraphQLContext.newContext().of(
+                ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT, true
+        ).build();
+        validationContext.getTraversalContext() >> traversalContext
+    }
+
+    def "Allow unique label directive"() {
+        given:
+        def query = """
+            query defer_query {
+              ... @defer(label: "name") {
+                human {
+                  name
+                }
+              }
+          }
+        """
+        Document document = new Parser().parseDocument(query)
+        LanguageTraversal languageTraversal = new LanguageTraversal()
+
+        when:
+        languageTraversal.traverse(document, new RulesVisitor(validationContext, [deferDirectiveLabel]))
+
+        then:
+        errorCollector.errors.isEmpty()
+
+    }
+
+    def "Defer directive label argument must be unique"() {
+        given:
+        def query = """
+            query defer_query {
+                dog {
+                    ... @defer(label: "name") {
+                        name 
+                    }
+                }
+                alien {
+                    ... @defer(label: "name") {
+                        name 
+                   }
+                }
+        
+            }
+        """
+
+        Document document = new Parser().parseDocument(query)
+        LanguageTraversal languageTraversal = new LanguageTraversal()
+
+        when:
+        languageTraversal.traverse(document, new RulesVisitor(validationContext, [deferDirectiveLabel]))
+
+        then:
+        !errorCollector.errors.isEmpty()
+        errorCollector.containsValidationError(ValidationErrorType.DuplicateIncrementalLabel)
+    }
+
+    def "Multiple use of Defer directive is valid"() {
+        given:
+        def query = """
+            query defer_query {
+                dog {
+                    ... @defer {
+                        name 
+                    }
+                    ... @defer {
+                        name 
+                    }
+                }
+            }
+        """
+        Document document = new Parser().parseDocument(query)
+        LanguageTraversal languageTraversal = new LanguageTraversal()
+
+        when:
+        languageTraversal.traverse(document, new RulesVisitor(validationContext, [deferDirectiveLabel]))
+
+        then:
+        errorCollector.errors.isEmpty()
+    }
+
+    def "Allow Multiple use of Defer directive with different labels"() {
+        given:
+        def query = """
+            query defer_query {
+                dog {
+                    ... @defer(label: "name") {
+                        name 
+                    }
+                    ... @defer(label: "nameAgain") {
+                        name 
+                    }
+                }
+            }
+        """
+        Document document = new Parser().parseDocument(query)
+        LanguageTraversal languageTraversal = new LanguageTraversal()
+
+        when:
+        languageTraversal.traverse(document, new RulesVisitor(validationContext, [deferDirectiveLabel]))
+
+        then:
+        errorCollector.errors.isEmpty()
+    }
+
+
+    def "Label cannot be an argument directive"() {
+        given:
+        def query = """
+            query defer_query(\$label: Int) {
+                ... @defer(label:\$label) {
+                    human {
+                      name
+                    }
+                }
+            }
+        """
+
+        Document document = new Parser().parseDocument(query)
+        LanguageTraversal languageTraversal = new LanguageTraversal()
+
+        when:
+        languageTraversal.traverse(document, new RulesVisitor(validationContext, [deferDirectiveLabel]))
+
+        then:
+        !errorCollector.errors.isEmpty()
+        errorCollector.containsValidationError(ValidationErrorType.WrongType)
+    }
+
+
+    def "Defer directive Label must be string"() {
+        given:
+        def query = """
+          query defer_query {
+            dog {
+                ... @defer(label: 1) {
+                    name 
+                }
+            }
+         }
+        """
+        Document document = new Parser().parseDocument(query)
+        LanguageTraversal languageTraversal = new LanguageTraversal()
+
+        when:
+        languageTraversal.traverse(document, new RulesVisitor(validationContext, [deferDirectiveLabel]))
+
+        then:
+        !errorCollector.errors.isEmpty()
+        errorCollector.errors.size() == 1
+        errorCollector.containsValidationError(ValidationErrorType.WrongType)
+    }
+
+    def "defer with null label should behave as if no label was provided"() {
+        def query = '''
+            query {
+                dog {
+                    ... @defer(label: null) {
+                        name
+                    }
+                }
+                cat {
+                    ... @defer(label: null) {
+                        name
+                    }
+                }
+            }
+        '''
+        Document document = new Parser().parseDocument(query)
+        LanguageTraversal languageTraversal = new LanguageTraversal()
+
+        when:
+        languageTraversal.traverse(document, new RulesVisitor(validationContext, [deferDirectiveLabel]))
+
+        then:
+        errorCollector.errors.isEmpty()
+    }
+
+
+        static List<ValidationError> validate(String query) {
+        def document = new Parser().parseDocument(query)
+        return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH)
+    }
+}
+
diff --git a/src/test/groovy/graphql/validation/rules/DeferDirectiveOnRootLevelTest.groovy b/src/test/groovy/graphql/validation/rules/DeferDirectiveOnRootLevelTest.groovy
new file mode 100644
index 0000000000..ec65ec1937
--- /dev/null
+++ b/src/test/groovy/graphql/validation/rules/DeferDirectiveOnRootLevelTest.groovy
@@ -0,0 +1,473 @@
+package graphql.validation.rules
+
+import graphql.ExperimentalApi
+import graphql.i18n.I18n
+import graphql.language.Document
+import graphql.parser.Parser
+import graphql.validation.LanguageTraversal
+import graphql.validation.RulesVisitor
+import graphql.validation.SpecValidationSchema
+import graphql.validation.ValidationContext
+import graphql.validation.ValidationErrorCollector
+import graphql.validation.ValidationErrorType
+import spock.lang.Specification
+
+class DeferDirectiveOnRootLevelTest extends Specification {
+
+    ValidationErrorCollector errorCollector = new ValidationErrorCollector()
+
+    def traverse(String query) {
+        Document document = new Parser().parseDocument(query)
+        ValidationContext validationContext = new ValidationContext(
+                SpecValidationSchema.specValidationSchema,
+                document,
+                I18n.i18n(I18n.BundleType.Validation, Locale.ENGLISH))
+        validationContext.getGraphQLContext().put(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT, true)
+
+        LanguageTraversal languageTraversal = new LanguageTraversal()
+        languageTraversal.traverse(document, new RulesVisitor(validationContext, [new DeferDirectiveOnRootLevel(validationContext, errorCollector)]))
+    }
+
+
+    def "Not allow defer on subscription root level"() {
+        given:
+        def query = """
+            subscription pets {
+                ... @defer {
+                    dog {
+                        name 
+                    }
+                }             
+            }
+        """
+
+        when:
+        traverse(query)
+
+        then:
+        !errorCollector.errors.isEmpty()
+        errorCollector.containsValidationError(ValidationErrorType.MisplacedDirective)
+
+    }
+
+    def "Not allow defer mutation root level "() {
+        given:
+        def query = """
+            mutation dog {
+                ... @defer {
+                    createDog(input: {id: "1"}) {
+                      name
+                    }
+                }
+            }
+        """
+
+
+        when:
+        traverse(query)
+
+        then:
+        !errorCollector.errors.isEmpty()
+        errorCollector.errors.size() == 1
+        errorCollector.containsValidationError(ValidationErrorType.MisplacedDirective)
+        errorCollector.errors.get(0).message == "Validation error (MisplacedDirective) : Defer directive cannot be used on root mutation type 'PetMutationType'"
+
+    }
+
+    def "Defer directive is allowed on query root level"() {
+        given:
+        def query = """
+          query defer_query {
+            ... @defer {
+                dog {
+                    name 
+                }
+            }
+         }
+        """
+        when:
+        traverse(query)
+
+        then:
+        errorCollector.errors.isEmpty()
+    }
+
+    def "Not allow defer mutation root level on inline fragments "() {
+        given:
+        def query = """
+           mutation doggo {
+              ... {
+                    ... @defer {
+                        createDog(input: {id: "1"}) {
+                            name
+                        }
+                    }
+                    
+                }       
+            }
+        """
+        when:
+        traverse(query)
+
+        then:
+        !errorCollector.errors.isEmpty()
+        errorCollector.errors.size() == 1
+        errorCollector.containsValidationError(ValidationErrorType.MisplacedDirective)
+        errorCollector.errors.get(0).message == "Validation error (MisplacedDirective) : Defer directive cannot be used on root mutation type 'PetMutationType'"
+    }
+
+    def "Not allow defer on subscription root level even when is inside multiple inline fragment"() {
+        given:
+        def query = """
+            subscription pets {
+                ...{ 
+                    ...{
+                        ... @defer {
+                            dog {
+                                name 
+                            }
+                        }
+                   }
+               }             
+            }
+        """
+        when:
+        traverse(query)
+
+        then:
+        !errorCollector.errors.isEmpty()
+        errorCollector.errors.size() == 1
+        errorCollector.containsValidationError(ValidationErrorType.MisplacedDirective)
+        errorCollector.errors.get(0).message == "Validation error (MisplacedDirective) : Defer directive cannot be used on root subscription type 'SubscriptionRoot'"
+
+    }
+
+
+    def "Not allow defer  on mutation root level even when ih multiple inline fragments split in fragment"() {
+        given:
+        def query = """
+            fragment doggo on PetMutationType {
+                ... {
+                     ... @defer {
+                        createDog(id: "1") {    
+                                id
+                            }
+                        }
+                }       
+            }
+            
+            mutation doggoMutation {
+                ...{
+                    ...doggo
+                }
+           }
+
+            
+        """
+        when:
+        traverse(query)
+
+        then:
+        !errorCollector.errors.isEmpty()
+        errorCollector.errors.size() == 1
+        errorCollector.containsValidationError(ValidationErrorType.MisplacedDirective)
+        errorCollector.errors.get(0).message == "Validation error (MisplacedDirective@[doggo]) : Defer directive cannot be used on root mutation type 'PetMutationType'"
+    }
+
+
+    def "Allows defer on mutation when it is not on root level"() {
+        given:
+        def query = """
+            mutation pets {
+                createDog(input: {id: "1"}) {
+                   ... @defer {
+                        name
+                    }
+                }       
+            }
+        """
+        Document document = new Parser().parseDocument(query)
+        LanguageTraversal languageTraversal = new LanguageTraversal()
+
+        when:
+        traverse(query)
+
+        then:
+        errorCollector.errors.isEmpty()
+    }
+
+    def "allow defer on fragment when is not on mutation root level"() {
+        given:
+        def query = """
+            mutation doggo {
+                ...{
+                    createDog(id: "1") {
+                        ...doggo
+                    }
+                }
+           }
+
+            fragment doggo on Dog {
+                ... @defer {
+                    id
+                }
+            }
+            
+        """
+        when:
+        traverse(query)
+
+        then:
+        errorCollector.errors.isEmpty()
+    }
+
+
+    def "allow defer on split fragment when is not on mutation root level"() {
+        given:
+        def query = """
+            mutation doggo {
+              ...doggoCreate 
+           }
+
+            fragment doggoCreate on PetMutationType {
+                  createDog(id: "1") {
+                        ...doggoFields
+                    }
+            }
+
+            fragment doggoFields on Dog {
+                ... @defer {
+                    id
+                }
+            }
+            
+        """
+        when:
+        traverse(query)
+
+        then:
+        errorCollector.errors.isEmpty()
+
+    }
+
+
+    def "Not allow defer subscription root level even when there are multiple subscriptions"() {
+        given:
+        def query = """
+            subscription pets {
+                dog {                   
+                    name                     
+                }             
+            }
+            subscription dog {
+                ... @defer {
+                    dog {
+                         name 
+                    }
+                }
+            }
+            
+            subscription morePets {
+                cat {                   
+                    name                     
+                }             
+            }
+        """
+        Document document = new Parser().parseDocument(query)
+        LanguageTraversal languageTraversal = new LanguageTraversal()
+
+        when:
+        traverse(query)
+
+        then:
+        errorCollector.containsValidationError(ValidationErrorType.MisplacedDirective)
+        errorCollector.errors.size() == 1
+
+    }
+
+    def "Not allow defer on mutation root level when there are multiple fragment levels regarless fragment order on query"() {
+        given:
+        def query = """
+
+            fragment createDoggoRoot on PetMutationType {
+                ... {
+                    ...createDoggo
+                }
+            }
+                    
+           mutation createDoggoRootOp {
+              ...createDoggoRoot
+            }
+            
+            fragment createDoggo on PetMutationType {
+                ... {
+                    ... @defer {
+                        createDog(input: {id: "1"}) {
+                            name
+                        }
+                    }
+                }             
+            }
+            
+        """
+
+        when:
+        traverse(query)
+
+        then:
+        !errorCollector.errors.isEmpty()
+        errorCollector.errors.size() == 1
+        errorCollector.containsValidationError(ValidationErrorType.MisplacedDirective)
+        errorCollector.errors.get(0).message == "Validation error (MisplacedDirective@[createDoggoRoot/createDoggo]) : Defer directive cannot be used on root mutation type 'PetMutationType'"
+
+    }
+
+    def "Not allow defer on mutation root level even when there are multiple fragments and operations"() {
+        given:
+        def query = """
+
+            fragment createDoggoLevel1 on PetMutationType {
+                ... {
+                    ... {
+                        ...createDoggoLevel2
+                    }
+                }             
+            }                
+            
+            fragment createDoggoLevel2 on PetMutationType {
+                ...createDoggo
+            }   
+            
+            fragment createDoggo on PetMutationType {
+                ... {
+                    ... @defer {
+                        createDog(input: {id: "1"}) {
+                            name
+                        }
+                    }
+                }             
+            }          
+            
+            query pets1 {
+                ... @defer {
+                    dog {
+                        name 
+                    }             
+                }                 
+            }              
+
+           mutation createDoggo {
+              ...createDoggoLevel1
+           }
+            
+        """
+
+        when:
+        traverse(query)
+
+        then:
+        !errorCollector.errors.isEmpty()
+        errorCollector.errors.size() == 1
+        errorCollector.containsValidationError(ValidationErrorType.MisplacedDirective)
+        errorCollector.errors.get(0).message == "Validation error (MisplacedDirective@[createDoggoLevel1/createDoggoLevel2/createDoggo]) : Defer directive cannot be used on root mutation type 'PetMutationType'"
+
+    }
+
+
+    def "Not allow defer on subscription root level even when defer(if == false) "() {
+        given:
+        def query = """
+            subscription pets{
+                ... @defer(if:false) {
+                    dog {
+                
+                        name 
+                    }
+                    nickname
+                }             
+            }
+        """
+        Document document = new Parser().parseDocument(query)
+        LanguageTraversal languageTraversal = new LanguageTraversal()\
+
+        when:
+        traverse(query)
+
+        then:
+        !errorCollector.errors.isEmpty()
+        errorCollector.errors.size() == 1
+        errorCollector.containsValidationError(ValidationErrorType.MisplacedDirective)
+        errorCollector.errors.get(0).message == "Validation error (MisplacedDirective) : Defer directive cannot be used on root subscription type 'SubscriptionRoot'"
+
+    }
+
+    def "Not allow defer on subscription root level when defer(if == true) "() {
+        given:
+        def query = """
+            subscription pets{
+                ... @defer(if:true) {
+                    dog {
+                                    
+                        name
+                        }
+                    nickname
+                }             
+            }
+        """
+
+        when:
+        traverse(query)
+
+        then:
+        errorCollector.errors.size() == 1
+        errorCollector.containsValidationError(ValidationErrorType.MisplacedDirective)
+        errorCollector.errors.get(0).message == "Validation error (MisplacedDirective) : Defer directive cannot be used on root subscription type 'SubscriptionRoot'"
+
+    }
+
+    def "Not allow defer on mutation root level even when if is variable that could have false as value "() {
+        given:
+        def query = """
+            mutation pets(\$ifVar:Boolean){
+                ... @defer(if:\$ifVar) {
+                    createDog(input: {id: "1"}) {                                    
+                       name
+                    }
+                }             
+            
+            }
+        """
+
+        when:
+        traverse(query)
+
+        then:
+        errorCollector.errors.size() == 1
+        errorCollector.containsValidationError(ValidationErrorType.MisplacedDirective)
+        errorCollector.errors.get(0).message == "Validation error (MisplacedDirective) : Defer directive cannot be used on root mutation type 'PetMutationType'"
+    }
+
+    def "Not allow defer on mutation root level when defer(if == true) "() {
+        given:
+        def query = """
+            mutation pets{
+                ... @defer(if:true) {
+                    createDog(input: {id: "1"}) {                                    
+                       name
+                    }
+                }             
+            }
+        """
+
+        when:
+        traverse(query)
+
+        then:
+        errorCollector.errors.size() == 1
+        errorCollector.containsValidationError(ValidationErrorType.MisplacedDirective)
+        errorCollector.errors.get(0).message == "Validation error (MisplacedDirective) : Defer directive cannot be used on root mutation type 'PetMutationType'"
+
+    }
+
+}
+
diff --git a/src/test/groovy/graphql/validation/rules/DeferDirectiveOnValidOperationTest.groovy b/src/test/groovy/graphql/validation/rules/DeferDirectiveOnValidOperationTest.groovy
new file mode 100644
index 0000000000..1430b7743b
--- /dev/null
+++ b/src/test/groovy/graphql/validation/rules/DeferDirectiveOnValidOperationTest.groovy
@@ -0,0 +1,366 @@
+package graphql.validation.rules
+
+import graphql.ExperimentalApi
+import graphql.i18n.I18n
+import graphql.language.Document
+import graphql.parser.Parser
+import graphql.validation.LanguageTraversal
+import graphql.validation.RulesVisitor
+import graphql.validation.SpecValidationSchema
+import graphql.validation.ValidationContext
+import graphql.validation.ValidationErrorCollector
+import graphql.validation.ValidationErrorType
+import spock.lang.Specification
+
+class DeferDirectiveOnValidOperationTest extends Specification {
+    ValidationErrorCollector errorCollector = new ValidationErrorCollector()
+
+    def traverse(String query) {
+        Document document = new Parser().parseDocument(query)
+        I18n i18n = I18n.i18n(I18n.BundleType.Validation, Locale.ENGLISH)
+        ValidationContext validationContext = new ValidationContext(SpecValidationSchema.specValidationSchema, document, i18n)
+        validationContext.getGraphQLContext().put(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT, true)
+        LanguageTraversal languageTraversal = new LanguageTraversal()
+        languageTraversal.traverse(document, new RulesVisitor(validationContext, [new DeferDirectiveOnValidOperation(validationContext, errorCollector)]))
+    }
+
+    def "Allow simple defer on query with fragment definition"() {
+        def query = '''
+            query {
+                dog {
+                    ... DogFields @defer
+                }
+            }
+            
+            fragment DogFields on Dog {
+                name
+            }
+        '''
+
+        when:
+        traverse(query)
+
+        then:
+        errorCollector.errors.isEmpty()
+    }
+
+    def "Allow simple defer on mutation with fragment definition"() {
+        def query = '''
+            mutation {
+                createDog(input: {name: "Fido"}) {
+                    ... DogFields @defer
+                }
+            }
+            
+            fragment DogFields on Dog {
+                name
+            }
+        '''
+
+        when:
+        traverse(query)
+
+        then:
+        errorCollector.errors.isEmpty()
+    }
+
+    def "Not allow defer on subscription operation"() {
+        given:
+        def query = """
+            subscription pets {
+                dog {
+                    ... @defer {                
+                        name 
+                    }
+                    nickname
+                }             
+            }
+        """
+
+
+        when:
+        traverse(query)
+
+        then:
+        !errorCollector.errors.isEmpty()
+        errorCollector.containsValidationError(ValidationErrorType.MisplacedDirective)
+
+    }
+
+
+    def "Allow defer(if:false) on subscription operation"() {
+        given:
+        def query = """
+            subscription pets {
+                dog {
+                    ... @defer(if:false) {                
+                        name 
+                    }
+                    nickname
+                }             
+            }
+        """
+
+
+        when:
+        traverse(query)
+
+        then:
+        errorCollector.errors.isEmpty()
+
+    }
+
+    def "Not allow simple defer on subscription with fragment definition"() {
+        def query = '''
+            subscription {
+                dog {
+                    ... DogFields @defer
+                }
+            }
+            
+            fragment DogFields on Dog {
+                name
+            }
+        '''
+
+        when:
+        traverse(query)
+
+        then:
+        !errorCollector.errors.isEmpty()
+        errorCollector.containsValidationError(ValidationErrorType.MisplacedDirective)
+
+    }
+
+    def "Not allow defer on fragment when operation is subscription"() {
+        given:
+        def query = """
+            fragment doggo on PetMutationType {
+                ... { 
+                    dog {    
+                        ... @defer {
+                            id
+                        }
+                        nickname
+                    }
+                    
+                }       
+            }
+            
+            subscription doggoMutation {
+                ...{
+                    ...doggo
+                }
+           }
+            
+
+        """
+        when:
+        traverse(query)
+
+        then:
+        !errorCollector.errors.isEmpty()
+        errorCollector.containsValidationError(ValidationErrorType.MisplacedDirective)
+
+    }
+
+    def "Allow defer(if:false) on fragment when operation is subscription"() {
+        given:
+        def query = """
+            fragment doggo on PetMutationType {
+                ... { 
+                    dog {    
+                        ... @defer(if:false) {
+                            id
+                        }
+                        nickname
+                    }
+                    
+                }       
+            }
+            
+            subscription doggoMutation {
+                ...{
+                    ...doggo
+                }
+           }
+            
+
+        """
+        when:
+        traverse(query)
+
+        then:
+        errorCollector.errors.isEmpty()
+
+    }
+
+    def "Not allow defer subscription even when there are multiple operations with multiple fragments"() {
+        given:
+        def query = """
+
+          fragment doggoSubscription on SubscriptionRoot {
+                ... { 
+                    dog {
+                        ...doggo
+                    }
+                }          
+            }
+            
+            query pets {
+                ... @defer {
+                    dog {
+                        name 
+                    }             
+                }     
+            }
+            
+            subscription pets2 {
+                   ...doggoSubscription        
+            }  
+            
+            query pets3 {
+                dog {
+                    name 
+                }             
+            }     
+            
+            fragment doggo on Dog{           
+                ... @defer {
+                    name 
+                }
+            }
+        """
+
+        when:
+        traverse(query)
+
+        then:
+        !errorCollector.errors.isEmpty()
+        errorCollector.errors.size() == 1
+        errorCollector.errors.get(0).getValidationErrorType() == ValidationErrorType.MisplacedDirective
+        errorCollector.errors.get(0).message == "Validation error (MisplacedDirective@[doggoSubscription/dog/doggo]) : Directive 'defer' is not allowed to be used on operation subscription"
+
+    }
+
+
+    def "Not allow defer subscription even when there are multiple operations and multiple fragments"() {
+        given:
+        def query = """
+            query pets {
+                ... @defer {
+                    dog {
+                        name 
+                    }             
+                }     
+            }
+            
+            subscription pets2 {
+                dog {
+                    ... @defer {
+                        name 
+                    }
+                }             
+            }  
+            
+         
+        """
+
+        when:
+        traverse(query)
+
+        then:
+        !errorCollector.errors.isEmpty()
+        errorCollector.errors.size() == 1
+        errorCollector.errors.get(0).getValidationErrorType() == ValidationErrorType.MisplacedDirective
+        errorCollector.errors.get(0).message == "Validation error (MisplacedDirective@[dog]) : Directive 'defer' is not allowed to be used on operation subscription"
+
+    }
+
+    def "Allows defer on mutation when it is not on root level"() {
+        given:
+        def query = """
+            mutation pets {
+                dog {
+                    ... @defer {
+                        name 
+                    }
+                }             
+            }
+        """
+        when:
+        traverse(query)
+
+        then:
+        errorCollector.errors.isEmpty()
+    }
+
+
+    def "Allow defer on subscription when defer(if == false) "() {
+        given:
+        def query = """
+            subscription pets{
+                dog {
+                    ... @defer(if:false) {                
+                        name 
+                    }
+                    nickname
+                }             
+            }
+        """
+
+        when:
+        traverse(query)
+
+        then:
+        errorCollector.errors.isEmpty()
+
+    }
+
+    def "Not allow defer on subscription when defer(if == true) "() {
+        given:
+        def query = """
+            subscription pets{
+                dog {
+                    ... @defer(if:true) {                
+                        name
+                        }
+                    nickname
+                }             
+            }
+        """
+
+        when:
+        traverse(query)
+
+        then:
+        errorCollector.errors.size() == 1
+        errorCollector.containsValidationError(ValidationErrorType.MisplacedDirective)
+        errorCollector.errors.get(0).message == "Validation error (MisplacedDirective@[dog]) : Directive 'defer' is not allowed to be used on operation subscription"
+
+
+    }
+
+    def "Allow defer when if is variable that could have false as value "() {
+        given:
+        def query = """
+            subscription pets(\$ifVar:Boolean){
+                dog {
+                    ... @defer(if:\$ifVar) {                
+                        name 
+                    }
+                    nickname
+                }             
+            }
+        """
+
+        when:
+        traverse(query)
+
+        then:
+        errorCollector.errors.isEmpty()
+    }
+
+
+
+}
\ No newline at end of file
diff --git a/src/test/groovy/graphql/validation/rules/UniqueOperationNamesTest.groovy b/src/test/groovy/graphql/validation/rules/UniqueOperationNamesTest.groovy
index 1fa03eb24c..c5fa1eb3d2 100644
--- a/src/test/groovy/graphql/validation/rules/UniqueOperationNamesTest.groovy
+++ b/src/test/groovy/graphql/validation/rules/UniqueOperationNamesTest.groovy
@@ -46,8 +46,8 @@ class UniqueOperationNamesTest extends Specification {
         }
 
         mutation dogOperation {
-            mutateDog {
-                id
+            createDog(input: {id: "1"}) {
+                name
             }
         }
         """.stripIndent()