skip to content

How does Spring Boot turn your .graphqls files into a working schema — what is GraphQlSource and how are fields wired to code?

level: middleimportance: should knowfreq 55%

answer

  1. GraphQlSource owns GraphQL + GraphQLSchema
  2. SDL -> TypeDefinitionRegistry + RuntimeWiring
  3. AnnotatedControllerConfigurer scans @QueryMapping/@SchemaMapping
  4. DataFetcher per field; PropertyDataFetcher default
  5. GraphQlSourceBuilderCustomizer for low-level

basics

~10 s

The 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 s

GraphQlSource 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
java
// 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

for a junior

Know GraphQlSource builds the schema and controllers resolve fields.

for a middle

Explain the SDL -> TypeDefinitionRegistry + RuntimeWiring -> GraphQLSchema pipeline and the role of AnnotatedControllerConfigurer.

for a senior

Discuss default PropertyDataFetcher, @SchemaMapping for nested fields, and how N+1 is handled outside base wiring.

for a principal

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.

context