How does Spring Boot turn your .graphqls files into a working schema — what is GraphQlSource and how are fields wired to code?
answer
- GraphQlSource owns GraphQL + GraphQLSchema
- SDL -> TypeDefinitionRegistry + RuntimeWiring
- AnnotatedControllerConfigurer scans @QueryMapping/@SchemaMapping
- DataFetcher per field; PropertyDataFetcher default
- GraphQlSourceBuilderCustomizer for low-level
basics
~10 sThe auto-configured GraphQlSource bean reads the SDL files, builds the GraphQLSchema, and applies the runtime wiring. Annotated controllers (@QueryMapping, @MutationMapping, @SchemaMapping) become the data fetchers for each schema field.
solid answer
~40 sGraphQlSource is the central Spring bean that holds the built graphql.GraphQL engine and its GraphQLSchema. Spring Boot's GraphQlAutoConfiguration creates it via a GraphQlSource.SchemaResourceBuilder: it locates SDL resources from spring.graphql.schema.locations, parses and merges them into a type registry, then applies runtime wiring — the mapping from each schema field to a DataFetcher. Two sources of wiring exist. First, annotated controllers: AnnotatedControllerConfigurer scans for @QueryMapping, @MutationMapping, @SubscriptionMapping, and @SchemaMapping methods and registers each as a DataFetcher. Second, any RuntimeWiringConfigurer beans you define, for custom scalars or type resolvers. For fields with no explicit resolver, a default PropertyDataFetcher reads the matching getter/property on the returned object. The result is a fully wired schema that GraphQlSource exposes to the transport (HTTP /graphql, WebSocket).
code
java · 22 lines// schema.graphqls:
// type Query { bookById(id: ID!): Book }
// type Mutation { addBook(title: String!): Book }
// type Book { id: ID! title: String! author: Author }
// type Author { id: ID! name: String! }
@Controller
class BookController {
@QueryMapping // Query.bookById
public Book bookById(@Argument String id) { ... }
@MutationMapping // Mutation.addBook
public Book addBook(@Argument String title) { ... }
// Book.author — resolved lazily per Book (avoids fetching authors you don't request)
@SchemaMapping(typeName = "Book", field = "author")
public Author author(Book book) {
return authorService.byId(book.getAuthorId());
}
// Book.id / Book.title need no method: PropertyDataFetcher reads getId()/getTitle()
}go deeper
Know GraphQlSource builds the schema and controllers resolve fields.
Explain the SDL -> TypeDefinitionRegistry + RuntimeWiring -> GraphQLSchema pipeline and the role of AnnotatedControllerConfigurer.
Discuss default PropertyDataFetcher, @SchemaMapping for nested fields, and how N+1 is handled outside base wiring.
Reason about GraphQlSourceBuilderCustomizer, schema transforms, inspection reports in CI, and wiring-source precedence.
**The pipeline, end to end.** 1. **Resource discovery.** `GraphQlAutoConfiguration` (in `spring-boot-autoconfigure`) reads `spring.graphql.schema.locations` (default `classpath:graphql/**/`) and `spring.graphql.schema.file-extensions` (default `.graphqls,.gqls`), resolving them to a set of `Resource`s. 2. **`GraphQlSource` build.** It constructs a `GraphQlSource` using `GraphQlSource.schemaResourceBuilder()`. `GraphQlSource` is the Spring abstraction that owns the executable `graphql.GraphQL` instance and its `GraphQLSchema`. The builder: parses each SDL resource into a **`TypeDefinitionRegistry`** (GraphQL-Java's parsed-but-not-executable representation), merges the registries, then combines them with a **`RuntimeWiring`** to produce the executable `GraphQLSchema`. 3. **Runtime wiring = field → DataFetcher.** SDL only declares *what* fields exist; it says nothing about *how* to fetch them. A **`DataFetcher`** (GraphQL-Java functional interface, `Object get(DataFetchingEnvironment)`) does the fetching. `RuntimeWiring` is the registry binding each `TypeName.fieldName` to a `DataFetcher`, plus custom scalars and `TypeResolver`s for interfaces/unions. 4. **Where wiring comes from in Spring.** - **Annotated controllers.** The auto-configured **`AnnotatedControllerConfigurer`** (itself a `RuntimeWiringConfigurer`) scans `@Controller` beans for `@QueryMapping`, `@MutationMapping`, `@SubscriptionMapping`, and `@SchemaMapping`. Each becomes a `DataFetcher`. `@QueryMapping foo()` binds field `foo` on type `Query`; `@SchemaMapping(typeName="Book", field="author")` binds a field on any type. Method arguments are bound with `@Argument`, `@Argument` on a class for input objects, `@ContextValue`, `Principal`, etc. - **`RuntimeWiringConfigurer` beans.** Any bean of this type is invoked with the `RuntimeWiring.Builder` so you can add custom scalars (`.scalar(...)`) or `TypeResolver`s. 5. **Default property fetching.** For a schema field with no explicit resolver, GraphQL-Java's **`PropertyDataFetcher`** reads a same-named property/getter from the parent object. So `Book.title` needs no code if the returned `Book` has `getTitle()`. **Customization hook.** For lower-level control (e.g. field-visibility, directive wiring, schema transformations) you register a **`GraphQlSourceBuilderCustomizer`** bean, which receives the `GraphQlSource.SchemaResourceBuilder` before the schema is built. **Gotchas.** - If a `@SchemaMapping`/`@QueryMapping` method name doesn't match a schema field and you didn't set `field=`, it silently isn't wired to what you expect — enable the **schema inspection report** (Boot logs unmapped fields at startup) to catch it. - Controllers must be Spring `@Controller` beans; a plain class with the annotations isn't scanned. - Batch/N+1 concerns are handled separately via `@BatchMapping` / `DataLoader`, not the base wiring. - Returning a type whose properties don't match the SDL field names yields `null`s from `PropertyDataFetcher` — names must align or you need an explicit `@SchemaMapping`.
- A schema field returns null even though the resolver ran — why?Likely the returned object's property name doesn't match the schema field, so PropertyDataFetcher can't read it. Add an explicit @SchemaMapping or align the getter name.
- What is the difference between @QueryMapping and @SchemaMapping?@QueryMapping is shorthand for @SchemaMapping(typeName="Query"). @SchemaMapping binds a field on any type (typeName/field), used for nested/derived fields like Book.author.
saying these in an interview costs you the question
- Thinking SDL alone executes queries — without a DataFetcher/property, fields resolve to null.
- Believing you must register every field manually — PropertyDataFetcher covers simple properties automatically.
- Claiming controllers don't need to be Spring beans.