Title: GraphQLSchema.FastBuilder for High-Performance Schema Construction · Issue #4196 · graphql-java/graphql-java · GitHub
Open Graph Title: GraphQLSchema.FastBuilder for High-Performance Schema Construction · Issue #4196 · graphql-java/graphql-java
X Title: GraphQLSchema.FastBuilder for High-Performance Schema Construction · Issue #4196 · graphql-java/graphql-java
Description: Proposal: GraphQLSchema.FastBuilder for High-Performance Schema Construction Human-written overview Airbnb's Viaduct platform is written on top of GraphQL Java. Airbnb's schema has grown to be over 25K types with over 120K "members" (fie...
Open Graph Description: Proposal: GraphQLSchema.FastBuilder for High-Performance Schema Construction Human-written overview Airbnb's Viaduct platform is written on top of GraphQL Java. Airbnb's schema has grown to be over...
X Description: Proposal: GraphQLSchema.FastBuilder for High-Performance Schema Construction Human-written overview Airbnb's Viaduct platform is written on top of GraphQL Java. Airbnb's schema has grown to...
Opengraph URL: https://github.com/graphql-java/graphql-java/issues/4196
X: @github
Domain: patch-diff.githubusercontent.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"GraphQLSchema.FastBuilder for High-Performance Schema Construction","articleBody":"# Proposal: GraphQLSchema.FastBuilder for High-Performance Schema Construction\n\n## Human-written overview\n\nAirbnb's [Viaduct](https://github.com/airbnb/viaduct) platform is written on top of GraphQL Java. Airbnb's schema has grown to be over 25K types with over 120K \"members\" (fields and enumeration values). At this scale the construction of GraphQL Java's `GraphQLSchema` object has become a bottleneck. Futher, at both build time and runtime Viaduct creates the same schema (and variants of it) many times, multiplying the performance issues we've seen in this construction.\n\nIn the Viaduct project itself we [addressed this problem](https://github.com/airbnb/viaduct/issues/254) in part by developing a file format that effectively eliminates all parsing-related overhead. By our measurements on `large-schema-4`:\n\n| Benchmark | Avg Time | Memory |\n|---------------------|----------|-----------|\n| From SDL Text | 481 ms | 626 MB/op |\n| From optimized file | 59 ms | 166 MB/op |\n\nThese numbers measure the time and space required to effectively create a `TypeDefinitionRegistry` - an 8x speedup and close to 4x reduction in space.\n\nHowever, it turns out that creating a `TypeDefinitionRegistry` consumes only half the time it takes overall to construct a `GraphQLSchema` object, so the improvements just reported resulted in just a 50% improvement. We were looking for something more significant, so we took a look at optimizing `GraphQLSchema.Builder`. This Builder is very general, in that it allows you to \"clear\" the builder mid-construction, to update the code-registry factory during construction, and to add just the \"roots\" of the schema versus adding every single type defined by the schema. Also, the Builder _always_ performs schema validation, which for systems like Viaduct that read the same schema over and over again is redundant and somewhat expensive.\n\nWe determined that the current semantics of this Builder were not something we should change, but also not semantics we could effectively optimize. So instead we've developed an alternative \"FastBuilder\", which is more restrictive in its semantics than is the current Builder, but in turn is significantly less resource intensive. On that same large schema:\n\n| Metric | Standard Builder | FastBuilder | Improvement |\n|--------|------------------|-------------|-------------|\n| **Build Time** | 1,933 ms | 278 ms | **6.96x faster** |\n| **Memory Allocated** | 2.71 GB/op | 411 MB/op | **85% reduction** |\n| **GC Time (total)** | 258 ms | 183 ms | 29% reduction |\n\nThis measures the time and space required to go from a `TypeDefinitionRegistry` to a `GraphQLSchema`.\n\nThe rest of this issue was written by an AI Agent - we've checked it for accuracy, but please forgive its length and pedantic tone.\n\n## Summary\n\nThis proposal introduces `GraphQLSchema.FastBuilder`, a high-performance alternative to the standard `GraphQLSchema.Builder` for programmatic schema construction. FastBuilder is not as general as Builder, imposing restrictions on its input that Builder does not. In return, FastBuilder eliminates multiple full-schema traversals during `build()`, achieving **7x faster build times** and **85% reduction in memory allocations** for large schemas.\n\n## Motivation\n\n### The Problem\n\nWhen building a `GraphQLSchema` using the standard `Builder.build()` method, several expensive full-schema traversals occur:\n\n1. **Type collection traversal** - Walks the entire type tree to collect all types via `SchemaUtil.visitPartiallySchema()`\n2. **Code registry wiring traversal** - Full traversal for data fetcher and type resolver wiring\n3. **Type reference replacement traversal** - Full traversal using `GraphQLTypeResolvingVisitor` to replace `GraphQLTypeReference` placeholders with actual types\n4. **Validation traversal** - Full schema validation (when enabled)\n\nFor schemas with thousands of types, these traversals compound to take significant time and allocate significant (transient) space during schema construction.\n\n### Relation to Recent graphql-java Optimizations\n\nThe graphql-java project has made progress on schema construction performance in recent releases:\n\n- **v25.0**: Eliminated CompletableFuture wrapping overhead, replaced stream operations with direct loops\n- **ImmutableTypeDefinitionRegistry** (PR #4021): Prevents unnecessary defensive copies during schema generation\n- **makeSchema Performance Fix** (PR #4020): Addressed repeated `typeRegistry.extensions()` allocations\n\nThese optimizations focuse on parsing and type definition handling, but the `Builder.build()` method still performs multiple full-schema traversals that become the dominant cost for programmatic schema construction.\n\nThe current PR complements the previous improvements, focusing on the overhead of `Builder.build()`. Compared to Builder, FastBuilder imposes some restrictions on its clients -- restrictions that are met by the standard TypeDefinitionRegistry path for schema construction -- restrictions that allow it to eliminate the overhead of schema traversal and thus significantly reduce time and space requirements.\n\n### Benchmark Results\n\nUsing the `large-schema-4.graphqls` test schema (~18,800 types) with JMH and GC profiling:\n\n| Metric | Standard Builder | FastBuilder | Improvement |\n|--------|------------------|-------------|-------------|\n| **Build Time** | 1,933 ms | 278 ms | **6.96x faster** |\n| **Memory Allocated** | 2.71 GB/op | 411 MB/op | **85% reduction** |\n| **GC Time (total)** | 258 ms | 183 ms | 29% reduction |\n\n\u003cdetails\u003e\n\u003csummary\u003eFull JMH Output\u003c/summary\u003e\n\n```\nBenchmark Mode Cnt Score Error Units\nBuildSchemaBenchmark.benchmarkBuildSchemaAvgTime avgt 6 1932.933 ± 82.237 ms/op\nBuildSchemaBenchmark.benchmarkBuildSchemaAvgTime:gc.alloc.rate avgt 6 1339.335 ± 55.255 MB/sec\nBuildSchemaBenchmark.benchmarkBuildSchemaAvgTime:gc.alloc.rate.norm avgt 6 2714194399.111 ± 7044683.852 B/op\nBuildSchemaBenchmark.benchmarkBuildSchemaAvgTime:gc.count avgt 6 30.000 counts\nBuildSchemaBenchmark.benchmarkBuildSchemaAvgTime:gc.time avgt 6 258.000 ms\nBuildSchemaBenchmark.benchmarkBuildSchemaAvgTimeFast avgt 6 277.800 ± 8.305 ms/op\nBuildSchemaBenchmark.benchmarkBuildSchemaAvgTimeFast:gc.alloc.rate avgt 6 1410.958 ± 51.101 MB/sec\nBuildSchemaBenchmark.benchmarkBuildSchemaAvgTimeFast:gc.alloc.rate.norm avgt 6 410974008.188 ± 3050567.714 B/op\nBuildSchemaBenchmark.benchmarkBuildSchemaAvgTimeFast:gc.count avgt 6 32.000 counts\nBuildSchemaBenchmark.benchmarkBuildSchemaAvgTimeFast:gc.time avgt 6 183.000 ms\n```\n\n**Benchmark Configuration:**\n- JMH 1.37, JDK 21.0.4 (OpenJDK Corretto)\n- Warmup: 2 iterations, 5s each\n- Measurement: 3 iterations, 10s each\n- Forks: 2\n- Profiler: GCProfiler\n\n\u003c/details\u003e\n\n## Use Cases\n\nFastBuilder is designed for scenarios where schemas are constructed programmatically from pre-built type objects:\n\n1. **Code-first schema generation** - Frameworks that programmatically construct types and need fast schema assembly\n2. **Schema caching/rebuilding** - Applications that rebuild schemas from cached type objects\n3. **Dynamic schema composition** - Systems that compose schemas from type registries or modules\n4. **High-throughput services** - Services where schema construction latency impacts startup time or request handling\n5. **GraphQL federation gateways** - Systems that merge multiple subgraph schemas into a supergraph\n\nFastBuilder is **not** intended to replace the standard builder for:\n\n- SDL-first development (use `SchemaParser` + `SchemaGenerator`)\n- Small schemas where construction time is negligible\n- Cases requiring automatic type discovery from root types\n- Scenarios requiring full validation on every build\n\n## Commit Structure\n\nTo help with reviewing of this change, we implemented as a stacked set of commits. Each commit has a descriptive message body explaining its contents.\n\n```\n fastbuilder (11 commits on top of assertValidName)\n └── Add FastSchemaGenerator and JMH benchmark infrastructure\n └── Phase 9: Validation and Edge Cases - FastBuilder implementation ← now includes FindDetachedTypes\n └── Phase 8: Union Types\n └── Phase 7: Interface Types\n └── Phase 6: Object Types\n └── Phase 5: Applied directives\n └── Phase 4: Input object types\n └── Phase 3: Enumeration types\n └── Phase 2: Directives\n └── Phase 1: Scaffolding\n │\n Optimize assertValidName\n │\n upstream/master\n```\n\nWe call out the `assertValidName` optimization because it's a improvement that should be considered on its own basis even if the rest of these changes are rejected.\n\n\n## Design\n\n### Architecture\n\nFastBuilder is implemented as a public static inner class of `GraphQLSchema`:\n\n```\nGraphQLSchema.FastBuilder\n├── typeMap: Map\u003cString, GraphQLNamedType\u003e # Explicit type registry\n├── directiveMap: Map\u003cString, GraphQLDirective\u003e # Directive definitions\n├── shallowTypeRefCollector: ShallowTypeRefCollector # Incremental type-ref tracking\n├── codeRegistryBuilder: GraphQLCodeRegistry.Builder # Code registry\n└── interfacesToImplementations: Map\u003c...\u003e # Interface-\u003eimpl mapping\n```\n\n### Key Design Principles\n\nFastBuilder takes an \"expert mode\" approach with stricter assumptions that enable incremental processing:\n\n- **Incremental work only**: When a type is added, only that node is examined (no traversals)\n- **Shallow scans**: Type references collected via local inspection, not recursive traversal\n- **Explicit types**: All named types must be explicitly provided via `additionalType()`\n- **Optional validation**: Validation is disabled by default (configurable via `withValidation(true)`)\n- **No clearing/resetting**: Types and directives cannot be removed once added\n\n### Supporting Classes\n\n1. **`ShallowTypeRefCollector`** (`@Internal`)\n - Performs shallow scans of types as they're added\n - Collects type reference replacement targets without recursive traversal\n - Tracks interface-to-implementation mappings incrementally\n - Handles all GraphQL type kinds: objects, interfaces, unions, inputs, enums, scalars\n\n2. **`FindDetachedTypes`** (`@Internal`)\n - DFS traversal to compute \"additional types\" (types not reachable from root types)\n - Considers types reachable from Query, Mutation, Subscription, and directive arguments\n - Used at schema construction time to populate `getAdditionalTypes()`\n\n### Build Process\n\nFastBuilder's `build()` method:\n\n```java\npublic GraphQLSchema build() {\n // Step 1: Replace type references using collected targets (no full traversal)\n shallowTypeRefCollector.replaceTypes(typeMap);\n\n // Step 2: Add built-in directives if missing (@skip, @include, etc.)\n addBuiltInDirectivesIfMissing();\n\n // Step 3: Create schema via new private constructor (no traversal)\n GraphQLSchema schema = new GraphQLSchema(this);\n\n // Step 4: Optional validation (only when explicitly enabled)\n if (validationEnabled) {\n List\u003cSchemaValidationError\u003e errors = new SchemaValidator().validateSchema(schema);\n if (!errors.isEmpty()) throw new InvalidSchemaException(errors);\n }\n\n return schema;\n}\n```\n\n## API\n\n```java\n@ExperimentalApi\npublic static final class FastBuilder {\n // Construction - requires code registry and at least Query type\n FastBuilder(GraphQLCodeRegistry.Builder codeRegistryBuilder,\n GraphQLObjectType queryType,\n @Nullable GraphQLObjectType mutationType,\n @Nullable GraphQLObjectType subscriptionType)\n\n // Type management - all types must be explicitly added\n FastBuilder additionalType(GraphQLType type)\n FastBuilder additionalTypes(Collection\u003c? extends GraphQLType\u003e types)\n\n // Directive management\n FastBuilder additionalDirective(GraphQLDirective directive)\n FastBuilder additionalDirectives(Collection\u003c? extends GraphQLDirective\u003e directives)\n\n // Schema-level directives\n FastBuilder withSchemaDirective(GraphQLDirective directive)\n FastBuilder withSchemaAppliedDirective(GraphQLAppliedDirective applied)\n\n // Metadata\n FastBuilder description(String description)\n FastBuilder definition(SchemaDefinition def)\n\n // Configuration\n FastBuilder withValidation(boolean enabled) // default: false\n\n // Build\n GraphQLSchema build()\n}\n```\n\n### Example Usage\n\n```java\n// Create types with type references for circular relationships\nGraphQLObjectType userType = newObject()\n .name(\"User\")\n .field(newFieldDefinition().name(\"id\").type(GraphQLID))\n .field(newFieldDefinition().name(\"posts\").type(list(typeRef(\"Post\"))))\n .build();\n\nGraphQLObjectType postType = newObject()\n .name(\"Post\")\n .field(newFieldDefinition().name(\"id\").type(GraphQLID))\n .field(newFieldDefinition().name(\"author\").type(typeRef(\"User\")))\n .build();\n\nGraphQLObjectType queryType = newObject()\n .name(\"Query\")\n .field(newFieldDefinition().name(\"user\").type(typeRef(\"User\")))\n .build();\n\n// Build schema with FastBuilder\nGraphQLCodeRegistry.Builder codeRegistry = GraphQLCodeRegistry.newCodeRegistry();\n\nGraphQLSchema schema = new GraphQLSchema.FastBuilder(codeRegistry, queryType, null, null)\n .additionalType(userType)\n .additionalType(postType)\n .build();\n```\n\n## Testing\n\nThe implementation includes comprehensive testing (132 tests across 4 test classes):\n\n### Test Strategy: Comparison-First\n\nThe primary validation approach compares schemas built with FastBuilder against identical schemas built via the standard SDL parsing path. This ensures semantic equivalence with real-world schema construction.\n\n### Test Organization\n\n| Test Class | Tests | Focus |\n|------------|-------|-------|\n| `FindDetachedTypesTest.groovy` | 24 | DFS traversal logic for additional types |\n| `FastBuilderComparisonAdditionalTypesTest.groovy` | 11 | `getAdditionalTypes()` equivalence |\n| `FastBuilderComparisonInterfaceTest.groovy` | 6 | Interface implementation tracking |\n| `FastBuilderComparisonTypeRefTest.groovy` | 18 | Type reference resolution |\n| `FastBuilderComparisonComplexTest.groovy` | 10 | Full schema equivalence |\n| `FastBuilderTest.groovy` | 53 | FastBuilder-specific behavior |\n\n### Verified Invariants\n\nFastBuilder-built schemas maintain these invariants with standard-built schemas:\n\n1. **Type map equivalence** - Same types accessible via `getType(name)`\n2. **Additional types** - Same set of \"detached\" types returned by `getAdditionalTypes()`\n3. **Interface implementations** - `getImplementations(interface)` returns same implementations in same alphabetical order\n4. **Directive definitions** - Same directive definitions available\n5. **Root types** - Query, Mutation, Subscription types match\n6. **Code registry** - Data fetchers and type resolvers properly wired\n\n## Breaking Changes\n\nNone. FastBuilder is a new, additive API. Existing code using `GraphQLSchema.Builder` is unaffected.\n\n## Implementation Notes\n\n- **Annotation**: Marked as `@ExperimentalApi` initially, allowing API refinement based on real-world feedback before hardening to `@PublicApi`\n- **Thread Safety**: FastBuilder is not thread-safe (same as standard Builder)\n- **Dependencies**: No new dependencies added (adheres to project policy)\n- **Code Style**: Follows graphql-java conventions (IntelliJ code style, no wildcard imports, etc.)\n\n## Related Work\n\n- graphql-java Issue #3939 - \"1% club\" micro-optimizations identifying builder pattern overhead\n- graphql-java Issue #3933 - ExecutionStrategyParameters memory allocation concerns\n- graphql-java PR #4021 - ImmutableTypeDefinitionRegistry\n- graphql-java PR #4020 - makeSchema performance fix\n\n","author":{"url":"https://github.com/rstata","@type":"Person","name":"rstata"},"datePublished":"2025-12-20T14:36:40.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":0},"url":"https://github.com/4196/graphql-java/issues/4196"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:bed3fb5e-badd-e11b-c4c5-29986e1f5435 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | A880:3FE5AD:A5E654:E2F71A:696FEFFE |
| html-safe-nonce | ae11e471ff0f6a423ad44f541413f3f21c124780a93349a2e662656613c85128 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJBODgwOjNGRTVBRDpBNUU2NTQ6RTJGNzFBOjY5NkZFRkZFIiwidmlzaXRvcl9pZCI6IjExMDQxOTcwNzM3OTI2NTk0NTQiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 67ca36564e0edd371a09434e24d677d3f1ba67524abd02fa2b3a25066b6d7518 |
| hovercard-subject-tag | issue:3750112509 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/graphql-java/graphql-java/4196/issue_layout |
| twitter:image | https://opengraph.githubassets.com/f23a25913f3d72c9880010050ffb2273fac252865121c8cee954e6dfabca549d/graphql-java/graphql-java/issues/4196 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/f23a25913f3d72c9880010050ffb2273fac252865121c8cee954e6dfabca549d/graphql-java/graphql-java/issues/4196 |
| og:image:alt | Proposal: GraphQLSchema.FastBuilder for High-Performance Schema Construction Human-written overview Airbnb's Viaduct platform is written on top of GraphQL Java. Airbnb's schema has grown to be over... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | rstata |
| hostname | github.com |
| expected-hostname | github.com |
| None | 2b218dbdee134592a2dbfabd454a1070986f1fbedb8334bf06b8f2ccc3449130 |
| turbo-cache-control | no-preview |
| go-import | github.com/graphql-java/graphql-java git https://github.com/graphql-java/graphql-java.git |
| octolytics-dimension-user_id | 14289921 |
| octolytics-dimension-user_login | graphql-java |
| octolytics-dimension-repository_id | 38602457 |
| octolytics-dimension-repository_nwo | graphql-java/graphql-java |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 38602457 |
| octolytics-dimension-repository_network_root_nwo | graphql-java/graphql-java |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | bcaac379a58a3ed99a1b0502e2a8f5cfd3a7b54b |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width