Skip to content

Empty union base definitions rejected before extensions are merged (spec violation) #4200

Closed

Description

@rstata

Describe the bug

The spec grammar:

(UnionTypeDefinition : Description? union Name Directives[Const]? UnionMemberTypes?)

makes UnionMemberTypes optional. SchemaParser correctly parses this SDL and TypeDefinitionRegistry correctly shows the union extensions. However, SchemaGenerator.makeExecutableSchema() fails validation with:

SchemaProblem{errors=[Union type 'Pet' must include one or more member types.]}

Root cause is that the validation checks the base definition before merging extension members (which isn't done for other types like interfaces). For deeper dive on root cause see this link.

(GraphQL Java 25.0)

To Reproduce

import graphql.schema.idl.SchemaParser
import graphql.schema.idl.UnExecutableSchemaGenerator
import org.junit.jupiter.api.Test

/**
 * Demonstrates a graphql-java bug: empty union base definitions with members
 * added via extensions are rejected, even though the GraphQL spec allows this.
 *
 * The spec grammar (UnionTypeDefinition : Description? union Name Directives[Const]? UnionMemberTypes?)
 * makes UnionMemberTypes optional. SchemaParser correctly parses this SDL and
 * TypeDefinitionRegistry correctly shows the union extensions. However,
 * SchemaGenerator.makeExecutableSchema() fails validation with:
 *
 *   SchemaProblem{errors=[Union type 'Pet' must include one or more member types.]}
 *
 * The validation checks the base definition before merging extension members.
 */
class UnionTest {
    @Test
    fun `test empty union base`() {
        val sdl = """
            type Cat { meow: String }
            type Dog { bark: String }
            union Pet
            extend union Pet = Cat | Dog
            type Query { pet: Pet }
        """.trimIndent()

        val registry = SchemaParser().parse(sdl)

        println("=== Registry parsed successfully ===")
        println("Types: ${registry.types().keys}")
        println("Union extensions: ${registry.unionTypeExtensions()}")

        try {
            val schema = UnExecutableSchemaGenerator.makeUnExecutableSchema(registry)
            println("=== Schema built successfully ===")
        } catch (e: Exception) {
            println("=== Exception ===")
            println("Class: ${e.javaClass.name}")
            println("Message: ${e.message}")
            e.printStackTrace()
            throw e
        }
    }
}

Activity

  1. rstata commented on Jan 4, 2026

    @rstata
    Author

    Java rewrite:

    import graphql.schema.idl.SchemaParser;
    import graphql.schema.idl.TypeDefinitionRegistry;
    import graphql.schema.idl.UnExecutableSchemaGenerator;
    import org.junit.jupiter.api.Test;
    
    class UnionTest {
    
      /**
       * Demonstrates a graphql-java bug: empty union base definitions with members
       * added via extensions are rejected, even though the GraphQL spec allows this.
       *
       * Expected failure (current graphql-java behavior):
       *
       *   SchemaProblem{errors=[Union type 'Pet' must include one or more member types.]}
       */
      @Test
      void testEmptyUnionBase() {
        String sdl = String.join("\n",
            "type Cat { meow: String }",
            "type Dog { bark: String }",
            "union Pet",
            "extend union Pet = Cat | Dog",
            "type Query { pet: Pet }"
        );
    
        TypeDefinitionRegistry registry = new SchemaParser().parse(sdl);
    
        System.out.println("=== Registry parsed successfully ===");
        System.out.println("Types: " + registry.types().keySet());
        System.out.println("Union extensions: " + registry.unionTypeExtensions());
    
        try {
          UnExecutableSchemaGenerator.makeUnExecutableSchema(registry);
          System.out.println("=== Schema built successfully ===");
        } catch (Exception e) {
          System.out.println("=== Exception ===");
          System.out.println("Class: " + e.getClass().getName());
          System.out.println("Message: " + e.getMessage());
          e.printStackTrace();
          throw e;
        }
      }
    }
    
  2. github-actions commented on Mar 5, 2026

    @github-actions
    Contributor

    Hello, this issue has been inactive for 60 days, so we're marking it as stale. If you would like to continue this discussion, please comment within the next 30 days or we'll close the issue.

  3. added 2 commits that reference this issue on Mar 9, 2026
    f740eb7
    6989cde
  4. github-actions commented on Apr 4, 2026

    @github-actions
    Contributor

    Hello, as this issue has been inactive for 90 days, we're closing the issue. If you would like to resume the discussion, please create a new issue.

  5. added a commit that references this issue on Apr 5, 2026
  6. andimarek commented on May 20, 2026

    @andimarek
    Member

    This is fixed in #4388

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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