How does Spring merge multiple .graphqls files, and how do you use `extend type` and split a large schema across files?
answer
- classpath:graphql/**/ merges all files
- base type once + extend type many times
- duplicate non-extend type = merge error
- extended field still needs a resolver
- GraphQlSourceBuilderCustomizer.schemaResources(...) / federation
basics
~20 sAll files matching classpath:graphql/**/ are parsed and merged into one schema, so you can split types across files by domain. To add fields to a type defined elsewhere, use extend type Query { ... }. The base type must be defined exactly once across the merged set.
solid answer
~50 sBecause the default location is classpath:graphql/**/, Spring reads every matching SDL file — across subfolders — parses each into a TypeDefinitionRegistry, and merges them into a single schema before building. This lets you organize SDL by domain: one file per bounded context, each contributing to the root types. The idiomatic pattern is that one file declares base `type Query`/`type Mutation`, and other files use `extend type Query { bookById(id: ID!): Book }` to append their fields; GraphQL requires the base type be declared once, with any number of `extend`s. You can also split object types the same way. Beyond files, GraphQlSourceBuilderCustomizer can add schema resources programmatically or from other locations, and Spring supports federation (spring-graphql federation support) when composing a subgraph. Care points: duplicate non-extend definitions of the same type fail merging, and every extended field still needs a resolver.
code
java · 22 lines// resources/graphql/base.graphqls
// type Query { ping: String }
// type Mutation { ping: String }
//
// resources/graphql/book/book.graphqls
// extend type Query { bookById(id: ID!): Book }
// extend type Mutation { addBook(title: String!): Book }
// type Book { id: ID! title: String! }
// Optional: add SDL from a non-default location programmatically.
@Bean
GraphQlSourceBuilderCustomizer extraSchema(
@Value("classpath:extra/legacy.graphqls") Resource legacy) {
return builder -> builder.schemaResources(legacy);
}
// Each extended field still needs a resolver:
@Controller
class BookController {
@QueryMapping Book bookById(@Argument String id) { ... }
@MutationMapping Book addBook(@Argument String title) { ... }
}go deeper
Know multiple files can exist and merge into one schema.
Use extend type Query to split root fields; know duplicate base types fail.
Organize SDL by domain, wire each extended field, add resources via GraphQlSourceBuilderCustomizer.
Reason about modular schema governance, composed-SDL artifacts in CI, module coupling via cross-type extends, and federation across services.
**Merging model.** The auto-configuration resolves `spring.graphql.schema.locations` (default `classpath:graphql/**/`) to *all* matching resources — the `**/` makes it recursive across subfolders — parses each into a GraphQL-Java **`TypeDefinitionRegistry`**, and **merges** the registries into one before generating the executable `GraphQLSchema`. Practically: drop as many `.graphqls` files as you like under `resources/graphql/` (including nested folders like `graphql/book/`, `graphql/author/`), and they compose into one schema. **Splitting by domain — the `extend` pattern.** GraphQL's root operation types (`Query`, `Mutation`, `Subscription`) must each be declared **once**. To let many files contribute root fields, one file declares the base and others *extend* it: ```graphql # base.graphqls type Query { _service: String } # or a placeholder / first real field # book.graphqls extend type Query { bookById(id: ID!): Book } extend type Mutation { addBook(title: String!): Book } type Book { id: ID! title: String! } ``` Rules of `extend`: - The base `type X { ... }` must appear **exactly once** across all merged files. - `extend type X { ... }` may appear any number of times and only **adds** fields (or interfaces/directives); it cannot redefine existing fields. - If two files both do `type Query { ... }` (not `extend`), merging fails with a duplicate-type error. - You can `extend` any object type, not just roots — useful when a cross-cutting module adds a field to a shared type. Field-name collisions across extends of the same type are errors. - If `type Mutation`/`type Subscription` don't exist yet, some setups still require a base declaration before extending; declare a base to be safe. **Every extended field still needs wiring.** `extend type Query { bookById... }` only declares the field; you still provide `@QueryMapping bookById(...)` (or a `DataFetcher`). The schema-mapping inspection report will flag extended fields left unimplemented. **Programmatic / non-classpath schemas.** For advanced composition — pulling SDL from a URL, another module's jar at a non-default path, or building parts in code — register a **`GraphQlSourceBuilderCustomizer`** and call `builder.schemaResources(...)` to add resources, or change `spring.graphql.schema.locations` to include extra locations (comma-separated). **Federation.** For large orgs splitting a graph across services, Spring for GraphQL offers **Apollo Federation** support (the `spring-graphql` federation module / `FederationSchemaFactory`), where each service owns a **subgraph** SDL and a gateway composes them. That is a distributed cousin of multi-file merging: instead of merging files in one app, you merge subgraph schemas across services, using `@key`/`extend`/entity resolvers. **Gotchas & governance (principal lens).** - Ordering of files doesn't matter for merging, but a missing base type does — enforce a convention (one `base.graphqls` owning root type stubs). - Splitting SDL improves modularity but can hide the full contract; teams often generate a *composed* SDL artifact in CI for review and for schema-registry breaking-change checks. - Keep module boundaries: a module extending another module's type couples them — decide deliberately, as with Spring Modulith module boundaries. - Custom scalars and type resolvers are still global to the merged schema; register them once via `RuntimeWiringConfigurer` regardless of how many files declare usages.
- Two files each contain `type Query { ... }`. What happens at startup?Merging fails with a duplicate type definition error. Exactly one file may declare the base `type Query`; the rest must use `extend type Query`.
- How would you compose a schema across multiple microservices rather than files in one app?Use Spring for GraphQL's Apollo Federation support: each service exposes a subgraph SDL with @key/entity resolvers, and a federated gateway composes them into one supergraph.
saying these in an interview costs you the question
- Thinking each file becomes a separate schema/endpoint — they merge into one.
- Redefining `type Query` in multiple files instead of using `extend`.
- Assuming `extend` auto-implements the field — it still needs a resolver.