skip to content

Spring for GraphQL

3 roadmaps34 questionsupdated

Spring's GraphQL support: schema-first development, annotated controllers, batch loading to avoid N+1, transports and interception, federation, error handling and cursor pagination. Interviewers ask about GraphQL wherever a single client needs flexible reads over many services.

on this pageshow

guide

overview

~1 min

Spring for GraphQL is the Spring portfolio's GraphQL server, built on the GraphQL Java engine and auto-configured by Spring Boot. You describe the API in a schema, bind its fields to annotated controller methods, and the framework executes each incoming query against that wiring. Interviewers bring it up when a design has one client needing flexible reads across many entities or services, and they probe whether you understand what GraphQL moves around: the client decides the shape of the response, so the server has to cope with query shapes it never planned for. The hub follows that path. [Schema-first development](/topics/be-spring-ecosystem-graphql-schema-first) covers the contract and how it is wired to code; [annotated controllers](/topics/be-spring-ecosystem-graphql-controllers) are where handlers live. [Batch loading and DataLoader](/topics/be-spring-ecosystem-graphql-batch-loading) answers the performance question GraphQL invites by design. [Transports and interception](/topics/be-spring-ecosystem-graphql-transports) explain how requests arrive and how headers and security reach a resolver. [Error handling](/topics/be-spring-ecosystem-graphql-errors) and [cursor-based pagination](/topics/be-spring-ecosystem-graphql-pagination) are where GraphQL conventions differ most from REST, and [federation](/topics/be-spring-ecosystem-graphql-federation) is the answer when many teams sit behind one API. Junior rounds stay on the mapping from schema to method. Senior and principal rounds turn to cost and ownership: batching, partial failures, paging under concurrent writes, and who owns which type once the schema is split across services. Learn the schema and controllers first; everything else in the hub is a refinement of that binding.

primer

### The schema is the contract The SDL files are the API. Clients, reviewers and tooling read them; Java or Kotlin code only supplies values for what they declare. Because nothing ties the two together at compile time, a mismatch shows up at startup or at request time rather than in the compiler, which is why interviewers ask how you keep schema and handlers honest. ### Every field has a resolver Execution walks the query tree and asks a data fetcher for each selected field. Root fields under `Query`, `Mutation` and `Subscription` map to controller methods; fields on other types either come straight from the parent object's properties or from a method that receives that parent. Thinking per field, not per endpoint, is the shift from REST that most other answers depend on. ### The client picks the shape, the server pays for it A client can nest lists inside lists, so a naive per-field lookup multiplies into one call per parent. Batching those lookups per request belongs in the design of any field that crosses a table or a service boundary, not in a later tuning pass. Depth, cost and page-size limits belong to the same conversation. ### Failure is per field One request can succeed in part. A failing field becomes null plus an entry in the `errors` list, while its siblings still return, and the transport status usually stays 200. Error design therefore means choosing classifications and messages clients can act on, and deciding what a client sees when something unexpected breaks. ### One execution, several transports Queries and mutations normally travel over HTTP; subscriptions need a connection that stays open. Interceptors sit in front of execution on every transport, which makes them the place to move headers, identities and correlation ids into the context a resolver can read. ### A graph can have many owners Federation splits one schema across services, each owning some types and extending others by key. It trades a single codebase for composition rules, a router and cross-service query plans — worth it when team boundaries, not load, are the problem.

SDL
Schema Definition Language: the textual syntax for GraphQL types, fields and operations. Spring for GraphQL reads it from .graphqls files on the classpath.
Data fetcher
The GraphQL Java callback that produces the value of one field. Annotated controller methods are registered as data fetchers for the fields they map.
GraphQlSource
The Spring bean that holds the built schema and its runtime wiring, and gives the rest of the framework access to the GraphQL Java engine.
N+1 problem
One call to fetch a list followed by a separate call per item to resolve a related field; GraphQL's nested queries make it easy to trigger.
DataLoader
A per-request utility that queues keys requested by many resolvers, loads them in one batch, deduplicates them and caches the results for that request.
Batch mapping
A controller method that resolves one field for a whole list of parents in a single call, backed by a DataLoader the framework registers.
Subscription
A GraphQL operation that returns a stream of results over time instead of one response, carried over a long-lived connection such as WebSocket.
Interceptor
A chained component wrapping each GraphQL request on a transport, able to read transport details, add context for resolvers and adjust the response.
GraphQLContext
A per-request map shared across the execution. Interceptors write values into it and controller methods can read them as parameters.
Partial response
A result carrying both data and errors: fields that failed are null and described in the errors list, while other fields keep their values.
Connection
The Relay pagination shape: a wrapper holding edges, each an item with its cursor, plus page information describing whether more results exist.
Cursor
An opaque string that marks a position in an ordered result, so the next page starts after it rather than after a row count.
Subgraph
One service's slice of a federated schema, owning some types and fields and able to contribute fields to entities defined elsewhere.
Entity
A federated type with a key that several subgraphs can reference and extend, resolved in each subgraph from that key.

Follow one request. It arrives on the HTTP endpoint (or, for a subscription, on a WebSocket or RSocket connection) and passes through the interceptor chain, which can lift headers or the authenticated user into the request context. The execution engine parses and validates the query against the schema that `GraphQlSource` built at startup, then resolves fields top-down: root fields call controller methods, nested fields call schema mappings or read properties of the parent. Whenever a resolver asks for data through a DataLoader, its key waits in a queue until the engine dispatches the whole level as one batch. Exceptions from any resolver pass through the exception-resolution chain and become entries in `errors` next to whatever data did resolve. The rest of the hub attaches to that path: - **Pagination** wraps selected list fields so a controller can return a scrolled window of results and the framework renders it as a connection with cursors. - **Security** usually authenticates at the endpoint with the ordinary filter chain and authorizes per method, relying on the context reaching resolvers that run asynchronously. - **Federation** adds entity resolution to a subgraph, so a router can ask this service for objects by key in the middle of a plan that spans others. The core binding, with the batching that keeps it from turning into N+1: ```kotlin @Controller class BookController(private val books: BookRepository, private val authors: AuthorClient) { @QueryMapping // Query.booksByGenre in schema.graphqls fun booksByGenre(@Argument genre: String): List<Book> = books.findByGenre(genre) @BatchMapping // Book.author for every book in the response, one call fun author(books: List<Book>): Map<Book, Author> { val byId = authors.findByIds(books.map { it.authorId }.toSet()) return books.associateWith { byId.getValue(it.authorId) } } } ``` The two methods meet only through the schema: nothing in the code says that `Book` has an `author` field, so renaming either side without the other breaks the binding, not the build.

  1. Schema-First Development →

    The contract comes first: SDL files, where they live and how the schema is built and wired to code.

  2. Annotated Controllers →

    How handler methods bind to query, mutation, subscription and nested fields, and how arguments reach them.

  3. Batch Loading & DataLoader →

    The N+1 problem and batching, the most common GraphQL performance question once controllers are clear.

  4. Error Handling →

    Partial responses and exception mapping, where GraphQL behaviour departs furthest from REST habits.

  5. Transports & Interception →

    Endpoints, subscriptions and interceptors: how headers and security context reach a resolver.

  6. Cursor-Based Pagination →

    Relay connections and cursor paging, which build on controllers and on keyset scrolling ideas.

  • Adding a nested field resolved per parent and forgetting batching; it looks fine in a test with three rows and falls over with a real list.

  • Saying a failed GraphQL request returns 4xx or 5xx: a resolver exception usually yields a 200 with null data for that field and an entry in errors.

  • Leaking exception messages to clients, or assuming Spring does it for you: unhandled exceptions are masked as internal errors, so useful errors need explicit mapping.

  • Reading security or headers from a thread-local inside a resolver and being surprised when async execution loses it; move values through interceptors and the request context.

  • Paging with offsets on a list that changes under the reader, or sorting cursor pages by a non-unique column, then wondering why rows repeat or vanish.

  • Proposing federation for a single team's service; its composition, router and cross-service query plans only pay off when ownership is split.

This guide assumes Spring for GraphQL on Spring Boot 3, from 1.3 onward. The project reached 1.0 in 2022 alongside Spring Boot 2.7, and several features interviewers ask about arrived later: - **1.2** added pagination support — connection types, scrolling subranges and adapters over Spring Data's scroll results — along with `@GraphQlExceptionHandler` methods and the schema inspection report that flags schema fields with no handler. - **1.3** added federation support, built on the federation library for GraphQL Java, with entity mapping methods on controllers. If an interviewer's codebase is older, say which features it would lack rather than assuming them. The annotation model for queries, mutations, schema mappings and batch mappings has been stable since 1.0, so answers about controllers and batching carry across every release line.

Spring for GraphQL sits on GraphQL Java, the engine that parses, validates and executes queries; Spring contributes the Boot auto-configuration, the annotated controller model, transports, interception, security integration and Spring Data support. Netflix DGS is the other widely used GraphQL framework for Spring applications, with its own annotation model and code generation around the same engine; a candidate should know both exist and that the concepts transfer. GraphQL itself is usually weighed against REST and gRPC. REST keeps HTTP caching, status codes and simple per-resource endpoints; gRPC suits typed service-to-service calls; GraphQL fits clients that assemble varied views from many entities in one round trip, at the price of harder caching and per-field cost control. In federated designs, Spring services act as subgraphs behind a router such as Apollo's, which owns composition and query planning. See [Federation Overview](/topics/be-spring-ecosystem-graphql-federation) for the subgraph side.

explore

report an issue with this guide →

questions

page 2 of 2

Why does Spring for GraphQL favor schema-first over code-first, and what are the trade-offs and customization hooks?

level: seniorimportance: nice to knowfreq 35%

basics

~20 s

Schema-first keeps the SDL contract as the single, human-readable, version-controlled source of truth, decoupled from Java. Trade-off: you hand-write and hand-sync SDL with resolvers; there's no compile-time link, so a schema inspection report catches mismatches.

open as a page

How is the whole cursor-connection pipeline wired in Spring for GraphQL — the beans Boot auto-configures, how to customize the cursor encoding or page-size limits, and how to support a non-Spring-Data container?

level: principalimportance: nice to knowfreq 15%

basics

~20 s

With Spring Data present, Boot auto-registers a ScrollPositionCursorStrategy, Window/Slice ConnectionAdapters, and a customizer that adds ConnectionFieldTypeVisitor. You customize by supplying your own CursorStrategy/CursorEncoder or ConnectionAdapter beans; for a foreign container, implement and register a custom ConnectionAdapter.

open as a page

How does Spring merge multiple .graphqls files, and how do you use `extend type` and split a large schema across files?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

All 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.

open as a page

You need cross-cutting logic (correlation IDs, response headers, auth context) applied consistently across HTTP, WebSocket, and RSocket GraphQL transports. How does Spring for GraphQL's interception model let you do this, and what are the ordering and transport-specific trade-offs?

level: principalimportance: nice to knowfreq 15%

basics

~20 s

Use WebGraphQlInterceptor for HTTP and WebSocket, and RSocketGraphQlInterceptor for RSocket. Each is a chained filter around execution where you read/write context and headers. Control order with Ordered/@Order; put shared logic in the GraphQLContext so all transports converge.

open as a page

showing 31–34 of 34