Skip to content

SimplePerformantInstrumentation hooks declares InstrumentationState non-null but createState returns null by default #4433

Description

@samuelAndalon

Describe the bug
In graphql-java 26, SimplePerformantInstrumentation is annotated with @NullMarked. Its unannotated InstrumentationState callback parameters are therefore exposed to Kotlin as non-null.

However, createState() and createStateAsync() remain explicitly nullable, and the default SimplePerformantInstrumentation.createState() implementation returns null.

This appears to be a nullability-contract mismatch introduced by the JSpecify instrumentation changes in #4272.

The callback state parameters should either be annotated @Nullable, matching the existing optional-state behavior, or graphql-java should guarantee a non-null empty state. Making the parameters nullable would preserve the existing runtime contract.

To Reproduce
Please provide a code example or even better a test to reproduce the bug.

  import graphql.ExecutionResult
  import graphql.GraphQL
  import graphql.execution.instrumentation.InstrumentationContext
  import graphql.execution.instrumentation.InstrumentationState
  import graphql.execution.instrumentation.SimplePerformantInstrumentation
  import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters
  import graphql.schema.idl.RuntimeWiring
  import graphql.schema.idl.SchemaGenerator
  import graphql.schema.idl.SchemaParser
  import kotlin.test.Test

  class InstrumentationStateNullabilityTest {

      private class StatelessInstrumentation : SimplePerformantInstrumentation() {
          override fun beginExecution(
              parameters: InstrumentationExecutionParameters,
              state: InstrumentationState
          ): InstrumentationContext<ExecutionResult>? = null
      }

      @Test
      fun `stateless Kotlin instrumentation should execute without an NPE`() {
          val typeRegistry = SchemaParser().parse(
              """
              type Query {
                  hello: String
              }
              """.trimIndent()
          )
          val schema = SchemaGenerator().makeExecutableSchema(
              typeRegistry,
              RuntimeWiring.MOCKED_WIRING
          )
          val graphQL = GraphQL.newGraphQL(schema)
              .instrumentation(StatelessInstrumentation())
              .build()

          graphQL.execute("{ hello }")
      }
  }

The execution fails when beginExecution is invoked:

  java.lang.NullPointerException:
  Parameter specified as non-null is null:
  method StatelessInstrumentation.beginExecution, parameter state

A current workaround is to return a non-null marker state:

private object EmptyInstrumentationState : InstrumentationState

 override fun createState(
      parameters: InstrumentationCreateStateParameters
  ): InstrumentationState = EmptyInstrumentationState

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions