Spring Boot: Getting Started with GraphQL

This section documents the current GraphQL specification (October 2021), plus the working draft for @defer/@stream/@oneOf, and the GraphQL-over-HTTP specification, as published at graphql.org and spec.graphql.org — which are the references these pages are written and verified against — and, for the integration pages, against Spring for GraphQL (2.0.x), Strawberry, Ariadne, and the client docs for Apollo Client / urql / Relay. No specific patch version is pinned. Some surfaces (Apollo Router/managed federation, graphql-ws internals, the Relay compiler internals, GraalVM native) are linked rather than documented in depth.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as GraphQL tooling iterates quickly.

Spring for GraphQL is Spring’s own integration layer on top of the reference graphql-java engine, and spring-boot-starter-graphql is what turns that integration into a handful of auto-configured beans and one working /graphql endpoint. This page covers adding the starter, where schema files live, what gets auto-configured, enabling GraphiQL for local development, the spring.graphql.* configuration surface, and how to inspect or print the schema Spring assembled.

Adding spring-boot-starter-graphql

A single starter pulls in Spring for GraphQL, the graphql-java engine it wraps, and (transitively) Spring Web — so it works alongside either the servlet stack (spring-boot-starter-web) or the reactive stack (spring-boot-starter-webflux), picking whichever transport is on the classpath at auto-configuration time:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

No version is declared explicitly here because Spring Boot’s dependency management (the parent POM or BOM) pins it — this page targets Spring for GraphQL 2.0.x as shipped alongside Spring Boot 4.x / Spring Framework 7.x, on a Java 17+ baseline, without pinning a specific patch release. Adding spring-boot-starter-websocket alongside the above additionally enables the WebSocket transport used for subscriptions, covered in Spring Boot: Transports, Security & Testing.

How Spring for GraphQL sits on top of graphql-java

graphql-java is the low-level reference implementation of the GraphQL specification for the JVM: it parses a schema document, validates and executes operations against it, and calls whatever DataFetcher a field is wired to — but it has no opinion on Spring, dependency injection, or HTTP. Spring for GraphQL is the layer that makes graphql-java feel native to a Spring application:

  • It builds the graphql.GraphQL instance for you from schema files on the classpath instead of hand-assembled RuntimeWiring.

  • It replaces manually registered DataFetcher lambdas with annotated @Controller methods (@QueryMapping/@MutationMapping/@SubscriptionMapping/@SchemaMapping), the subject of Spring Boot: Controllers.

  • It exposes the transport plumbing — HTTP, WebSocket, RSocket — as auto-configured Spring beans rather than something the application wires up by hand.

  • It integrates Spring’s own request-scoped context (Principal, @ContextValue) and DataLoader batching (BatchLoaderRegistry, covered in Spring Boot: Data Loading & Integration) into `graphql-java’s per-execution context.

Every concept from earlier pages in this section — the schema, resolvers, the request lifecycle — still applies unchanged; Spring for GraphQL only changes how those pieces are declared and wired together on a Spring Boot application. See Spring for GraphQL — Boot Starter for the full auto-configuration reference this page summarizes.

Schema files under src/main/resources/graphql/**.graphqls

By default, the starter looks for schema files anywhere under src/main/resources/graphql/, matching the .graphqls (or .graphql) extension, and merges every file it finds into a single schema — so a schema is commonly split by concern (schema.graphqls, book.graphqls, author.graphqls) rather than kept in one large file:

# src/main/resources/graphql/schema.graphqls
type Query {
    book(id: ID!): Book
    books: [Book!]!
}

type Mutation {
    addBook(input: AddBookInput!): Book!
}

type Book {
    id: ID!
    title: String!
    publishedYear: Int
    author: Author!
}

type Author {
    id: ID!
    name: String!
    books: [Book!]!
}

input AddBookInput {
    title: String!
    authorId: ID!
}

The lookup location is itself a configuration property (spring.graphql.schema.locations, see below), so a project can point at a different classpath directory, or several, if it needs to. `graphql-java’s own SDL grammar and type system are exactly what Schema & Type System already covers — nothing about the grammar changes when the file is read by Spring’s auto-configuration instead of hand-parsed.

Auto-configuration: GraphQlSource and ExecutionGraphQlService

Two beans anchor everything the starter wires up. GraphQlSource is responsible for building the executable graphql.GraphQL instance — parsing the merged schema files, applying any RuntimeWiringConfigurer beans (including the ones that register @Controller handler methods as DataFetcher`s), and exposing the resulting schema for both execution and introspection. `ExecutionGraphQlService is the transport-agnostic entry point that actually executes a request against that GraphQL instance: it accepts a GraphQlRequest, runs it through the interceptor chain described below, and returns a GraphQlResponse — independent of whether the request arrived over HTTP, WebSocket, or RSocket.

Everything above ExecutionGraphQlService is transport-specific and equally auto-configured. On the servlet stack (spring-boot-starter-web on the classpath), Spring Boot registers a GraphQlHttpHandler behind an HttpGraphQlHttpHandler-backed servlet mapping; on the reactive stack (spring-boot-starter-webflux), the equivalent RouterFunction is registered instead. Either way, an application gets a working POST /graphql endpoint with no explicit @Controller or @Bean of its own — only schema files and, later, resolver methods are the application’s own responsibility. Serving GraphQL over HTTP covers the wire format this endpoint speaks; this page only covers how that endpoint comes to exist.

Trying the endpoint

With only the schema file above on the classpath and no resolver written yet, the auto-configured endpoint already exists and already validates requests against that schema — it just has no DataFetcher behind Query.book yet, so a syntactically valid query against a field the schema doesn’t declare fails validation before execution, while a query against a real field with no resolver returns null for that field rather than an error:

curl -s http://localhost:8080/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query":"{ book(id: \"1\") { title } }"}'
{
  "data": {
    "book": null
  }
}

Spring Boot: Controllers is what fills in that null with a real @QueryMapping method; this page’s endpoint is otherwise already indistinguishable from a hand-wired one once a resolver exists.

Enabling GraphiQL

The starter bundles GraphiQL (introduced in Getting Started) but ships it disabled by default. Turning it on for local development exposes an in-browser query editor at /graphiql, pre-pointed at the application’s own /graphql endpoint:

spring:
  graphql:
    graphiql:
      enabled: true
      path: /graphiql        # default shown; override if it collides with another route

A production deployment typically leaves this false (or gates the path behind authentication) for the same reason Serving GraphQL over HTTP gives for disabling any bundled IDE on a public endpoint: an interactive schema browser widens the attack surface covered in Security and Demand Control.

spring.graphql.* configuration properties

The properties an application reaches for most often when getting a server running:

Property Purpose

spring.graphql.path

The HTTP endpoint path. Defaults to /graphql.

spring.graphql.schema.locations

Classpath location(s) to scan for .graphqls files. Defaults to classpath:graphql/*/.

spring.graphql.schema.file-extensions

File extensions treated as schema files. Defaults to .graphqls,.gqls.

spring.graphql.schema.printer.enabled

Exposes the assembled schema’s SDL over HTTP for inspection (see below). Defaults to false.

spring.graphql.graphiql.enabled / .path

Enables the bundled GraphiQL IDE and its path, as shown above. Defaults to false / /graphiql.

spring.graphql.websocket.path

Enables the WebSocket transport (subscriptions) at the given path when set; unset by default.

spring.graphql.cors.*

CORS configuration scoped to the GraphQL endpoint specifically, mirroring ordinary Spring MVC/WebFlux CORS properties.

spring.graphql.schema.introspection.enabled

Whether the schema can be queried via introspection at runtime, covered next. Defaults to true.

spring.graphql.websocket.connection-init-timeout

How long a WebSocket transport waits for the subscription protocol’s connection_init message before closing the socket. Defaults to 60s.

management.endpoint.graphql.enabled

A Spring Boot Actuator property (not spring.graphql.* itself) that exposes the schema-printing endpoint mentioned above through Actuator’s own management port instead of the main one. Defaults to false.

spring:
  graphql:
    path: /graphql
    schema:
      locations: classpath:graphql/**/
      printer:
        enabled: true
    graphiql:
      enabled: true
    cors:
      allowed-origins: "https://app.example.com"
      allowed-methods: POST

See Spring for GraphQL — Boot Starter for the complete, versioned property list — this table covers only what a project reaches for while first setting a server up.

Schema inspection and printing

Two related but distinct capabilities help verify that the schema Spring assembled is what the application intended. Schema printing (spring.graphql.schema.printer.enabled=true) exposes the fully merged SDL — exactly as graphql-java sees it after combining every *.graphqls file — at POST to the endpoint path with an empty query and the getSchema extension, or more simply via management.endpoint.graphql.enabled=true under Spring Boot Actuator, which surfaces it as an ordinary management endpoint. Printing the live schema back out is the fastest way to confirm that every file under spring.graphql.schema.locations was actually picked up and merged as expected.

Schema inspection, separately, runs at application startup: Spring for GraphQL’s SchemaMappingInspector walks the assembled schema and cross-checks it against the registered @Controller handler methods, logging a warning for any schema field that has no corresponding DataFetcher (and, conversely, flagging Java return-type mismatches it can detect statically). This is the fastest way to catch a typo in a `@SchemaMapping’s field name, or a schema field nobody wired a resolver for, before a client ever hits it at runtime rather than after. Both mechanisms are covered in more operational depth at Spring for GraphQL — Request Execution.

Runtime introspection (spring.graphql.schema.introspection.enabled) is a separate, client-facing capability from either of the above — it is the schema/type mechanism Introspection already covers, left enabled by default so tools like GraphiQL can discover the schema on their own. Turning it off outside development is one of several defenses covered in Security and Demand Control, not something specific to getting a server running in the first place.

The request pipeline

Once the starter has wired all of the above together, an HTTP (or WebSocket) request flows through the same fixed pipeline regardless of which transport carried it in:

flowchart LR A[HTTP or WebSocket transport] --> B[WebGraphQlInterceptor chain] B --> C[ExecutionGraphQlService] C --> D[graphql-java engine] D --> E[DataFetcher / SchemaMapping methods] E --> F[BatchLoaderRegistry]

The transport layer (the GraphQlHttpHandler/RouterFunction auto-configured above) decodes the incoming request into a transport-neutral WebGraphQlRequest. Every request then passes through the WebGraphQlInterceptor chain — an application’s own hook for cross-cutting concerns such as authentication context propagation or request logging, applied uniformly whether the request arrived over HTTP or WebSocket. ExecutionGraphQlService hands the request to the underlying graphql-java engine, which parses, validates, and executes it exactly as Getting Started's request-lifecycle diagram describes, invoking each field’s DataFetcher — in a Spring for GraphQL application, almost always an @Controller method annotated @QueryMapping/@MutationMapping/@SubscriptionMapping/@SchemaMapping, the subject of the next page. Any of those methods that need to batch per-field lookups register with the BatchLoaderRegistry, which Spring Boot: Data Loading & Integration covers as the JVM’s answer to the N+1 problem introduced in Performance and N+1.