0% found this document useful (0 votes)
459 views

Andreas Marek_ Donna Zhou - GraphQL With Java and Spring-Leanpub (2023)

Uploaded by

johnroma
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
459 views

Andreas Marek_ Donna Zhou - GraphQL With Java and Spring-Leanpub (2023)

Uploaded by

johnroma
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 303

GraphQL with Java and

Spring
Andreas Marek

Donna Zhou
GraphQL with Java and
Spring
GraphQL with Java and Spring
Prologue
Andi (Andreas)
Donna
About this book
Introduction
Your first Spring for GraphQL service
What is GraphQL?
A brief history of GraphQL
From GraphQL Java to Spring for GraphQL
Overview
Three layers
Schema and SDL
GraphQL query language
Request and Response
Execution and DataFetcher
How concepts relate to each other
GraphQL Java
Spring for GraphQL
Schema
Schema-first
Loading schema resources in Spring for GraphQL
GraphQL schema elements
GraphQL types
Fields everywhere
Scalar
Enum
Object
Input object
Interface
Union
List and NonNull
Directives
Arguments
Documentation with descriptions
Comments
GraphQL query language
Literals
Operations
Query operations
Mutation operations
Subscription operations
Arguments
Fragments
Inline fragments
Variables
Aliases
GraphQL document
Named and unnamed operations
Query language in GraphQL Java
DataFetchers
Spring for GraphQL annotated methods
PropertyDataFetchers in Spring for GraphQL
DataFetchers and schema mapping handler methods
TypeResolver in Spring for GraphQL
Arguments in Spring for GraphQL
More Spring for GraphQL inputs
Adding custom scalars in Spring for GraphQL
Under the hood: DataFetchers inside GraphQL Java
DataFetchers in GraphQL Java
Source objects in GraphQL Java
RuntimeWiring in GraphQL Java
Creating an executable schema in GraphQL Java
TypeResolver in GraphQL Java
Building a GraphQL service
Spring for GraphQL
GraphQL Java
Spring WebFlux or Spring MVC
Reading schemas
Configuration properties
Expanding our Spring for GraphQL service
Pet schema
Controllers
Fetching data from an external service
Source object
GraphQL arguments
Mutations
Unions, interfaces, and TypeResolver
Subscriptions
Getting started
Execution
Protocol
Client support
Request and response
Transport protocols and serialization
Request
Response
HTTP status codes
HTTP headers
Intercepting requests
GraphQL errors
Request errors
Field errors
How errors appear in the response
Error classifications
How to return errors
Throw exception during DataFetcher invocation
Customizing exception resolution
Return data and errors with DataFetcherResult
Schema design
Schema-first and implementation-agnostic
Evolution over versioning
Connected
Schema elements are cheap
Nullable fields
Nullable input fields and arguments
Pagination for lists with Relay’s cursor connection specification
Relay’s cursor connections specification
Schema
Query and response
Requesting more pages
Key concepts of Relay’s cursor connections specification
Expected errors
Mutation format
Naming standards
DataFetchers in depth
More DataFetcher inputs
Global context
Local context
DataFetcher implementation patterns
Spring for GraphQL Reactor support
Directives
Schema and operation directives
Built-in directives
@skip and @include
@deprecated
@specifiedBy
Defining your own schema and operation directives
Defining schema directives
Defining operation directives
Repeatable directives
Implementing logic for schema directives
Changing execution logic with schema directives
Validation with schema directives
Adding metadata with schema directives
Implementing logic for operation directives
Execution
Initializing execution objects
How Spring for GraphQL starts execution
Execution steps
Parsing and validation
Coercing variables
Fetching data
Reactive concurrency-agnostic
Completing a field
TypeResolver
Query vs mutation
Instrumentation
Instrumentation in Spring for GraphQL
Writing a custom instrumentation
InstrumentationContext
InstrumentationState
ChainedInstrumentation
Built-in instrumentations
List of instrumentation hooks
DataLoader
The n+1 problem
Solving the n+1 problem
DataLoader overview
DataLoader and GraphQL Java
DataLoader and Spring for GraphQL
@BatchMapping method signature
Testing
Unit testing DataFetcher
GraphQlTester
document or documentName
GraphQlTester.Request and execute
GraphQlTester.Response, path, entity, entityList
errors
Testing different layers
End-to-end over HTTP
Application test
WebGraphQlHandler test
ExecutionGraphQlService test
Focused GraphQL testing with @GraphQlTest
Subscription testing
Testing recommendations
Security
Securing a Spring for GraphQL service
Spring for GraphQL support for security
Method security
Testing auth
Java client
HTTP client
WebSocket client
GraphQlClient
GraphQL with Java and
Spring
Prologue

Andi (Andreas)
In 2015, I (Andi) was working as a software developer for a small
company in Berlin, Germany. During a conversation, one of my
colleagues (thanks a lot Stephan!) mentioned to me this new
technology called “GraphQL”, aimed at improving the way clients
access data from a service, and they planned to release it soon.

After looking into GraphQL, it immediately convinced me of the


value it could provide. Of course, I could not predict its success, but
I experienced firsthand in multiple companies the struggles of
creating and maintaining a REST API. Despite serious effort, they
all looked more like GraphQL over time rather than how a good
REST API was supposed to look. Perhaps it came from a lack of
understanding REST, but after I had witnessed very similar
challenges again and again in very different contexts, I thought
otherwise.

Being convinced of the value, I started immediately working on a


Java implementation after they released the GraphQL specification.
After about two weeks of investing all the free time I had outside
my job, I released the first version of GraphQL Java.

While it was just me in the beginning, shortly after I received the


first PR that fixed a typo. Today more than 200 people have
contributed to GraphQL Java and without them there would be no
GraphQL Java. Sincerely, thanks a lot to all of you.

Many thanks and a special mention belongs to Brad Baker, who has
been a co-maintainer for over six years. There is no way to overstate
his contributions and influence on GraphQL Java. It is as much his
project as it is mine.

Most importantly I want to thank my wife Elli for all her support:
without her there would be no book today.

Donna
I (Donna) am thrilled to write this book with Andi, who created
GraphQL Java and played a major role in the creation of Spring for
GraphQL. Andi, Brad, and I are the maintainers of GraphQL Java.

I discovered programming later in life, initially it was only a hobby.


One of the first programming ideas I learned about was open source
software. As a profit-maximising investment banker, it seemed
delightfully nuts that high quality work could be happily given away
for free. I adored the spirit of collaboration and community in the
open source world. I adored it so much that I changed careers and
became a software engineer.

Thanks to everyone in the GraphQL Java community for your


contributions over the years.

About this book


This book is for anyone who wants to build a production GraphQL
service with Java. By the end of this book, you will be confident
building your own production GraphQL service with Spring for
GraphQL, the official Spring integration built on top of the GraphQL
Java engine. Spring for GraphQL makes it easier than ever to build a
GraphQL service by eliminating boilerplate code and seamlessly
integrating with the Spring ecosystem.

GraphQL Java is the dominant Java implementation of GraphQL,


powering services at Twitter, AirBnB, Netflix, Atlassian, and many
other companies. By the end of this book, you’ll be leveraging the
same engine with Spring for GraphQL.

In this book, you’ll learn key GraphQL concepts, paired with


practical advice from our experiences running production GraphQL
services at scale. At the end of this book, you’ll have in depth
knowledge of Spring for GraphQL and the GraphQL Java engine, so
you will have the confidence to run production ready GraphQL
services.

This book is suitable for beginners building their first production


GraphQL service. There are also advanced topics later in the book
for intermediate readers.

We do not assume any prior knowledge of GraphQL. To make the


most of this book, we assume basic Java knowledge, and we assume
very basic Spring knowledge such as familiarity with the @Component
annotation. Optionally, if you intend to build a reactive service, you
should be familiar with the Reactor concepts of Mono and Flux.

All code examples were written with Java 17, which is the minimum
version required for Spring Boot 3.x. Examples in this book were
written with Spring Boot 3.0.4 and Spring for GraphQL 1.1.2, which
uses GraphQL Java 19.2.

If you have feedback or comments on the book, please let us know


via email at [email protected].

We deeply hope you enjoy this book.


Special thanks to our reviewers for giving us fantastic feedback.
Thanks to Rossen Stoyanchev and Brian Clozel from the Spring for
GraphQL team, our technical reviewer Doug Warren, our reviewers
Brad Baker, Antoine Boyer, Felipe Reis, Stephan Behnke, and Josh
Long. Thanks to our cover designer Mike Riethmuller.
Version: 0aab44c

Date: Sun May 14 23:18:20 UTC 2023


Introduction

Your first Spring for GraphQL service


The best way to get a feeling for GraphQL is to experience it. We
will walk through how to create your first Spring for GraphQL
service, step by step. In the coming chapters, we will explain these
steps in greater detail.

First, create a new project with Spring Initializr, at


https://start.spring.io. In this book, we will use Spring Boot 3.x
which requires at least Java 17. If you prefer to use Java 11, select a
Spring Boot version of at least 2.7.0 to use Spring for GraphQL. You
can choose between a Maven or Gradle project.

In the dependencies section, add Spring for GraphQL. We’ll then


need to add one more dependency for underlying transport. You can
choose either Spring Reactive Web (WebFlux) or Spring Web
(Spring MVC). In this book, we’ll be using Spring Reactive Web to
make use of the WebFlux framework and the Netty server for
reactive services. If you choose Spring Web (which includes Spring
MVC), all content in this book is still applicable to your service, as
Spring for GraphQL fully supports both Spring MVC and WebFlux.
All examples in this book are almost identical for Spring MVC, the
only difference is that controller methods will not be wrapped in a
Mono or Flux.
You can add your own project metadata, or follow along with our
example in the screenshot “Spring Start”.

Spring Start

Click Generate at the bottom of the page to generate your project.


Open the project in your favourite code editor. Start the application
from the main method in myservice.service.ServiceApplication. It will
start an HTTP endpoint at http://localhost:8080, but not much else
yet!

Let’s make this service more useful and implement a very simple
GraphQL service, which serves pet data. To keep this initial example
simple, the data will be an in-memory list. Later, in the Building a
GraphQL service chapter, we’ll extend this example to call another
service.
For this initial example, we’ll cover concepts at a high level, so we
can quickly arrive at a working service you can interact with. In the
coming chapters, we will explain these concepts in greater detail.

Let’s start by creating a GraphQL schema, which is a static


description of the API. Create a new subdirectory “graphql” under
“resources”. Paste the following into a new file
src/main/resources/graphql/schema.graphqls.

type Query {
pets: [Pet]
}

type Pet {
name: String
color: String
}

This GraphQL schema for pets is written in Schema Definition


Language (SDL) format. This defines a GraphQL query type with a
pets field, which returns a list of pets. Each pet has a name and
color, and both fields are string attributes. In the Schema chapter,
we’ll discuss GraphQL schema elements in depth.

Add a Pet record class in the package myservice.service.


package myservice. service;

record Pet( String name, String color) {


}

Next, we’ll add the logic to connect our schema with the pet data.
Create a new Java class PetsController in the package
myservice.service.

package myservice. service;

import org. springframework. graphql. data. method. annotation


.QueryMapping;
import org. springframework. stereotype. Controller;
import java. util. List;

@Controller
class PetsController {

@QueryMapping
List< Pet> pets() {
return List.of(
new Pet( "Luna", "cappuccino"),
new Pet( "Skipper", "black"));
}

And that’s all the code we need: a schema file, a record class, and a
GraphQL controller! The @QueryMapping controller annotation
registers the pets method as a DataFetcher, connecting the pets field
in the schema to data, in this case an in-memory list. We’ll explain
how to connect your schema and your data in much more detail in
the DataFetchers chapter.

To add a visual frontend to explore our API, enable the built-in


GraphiQL interactive playground by adding
spring.graphql.graphiql.enabled=true to the application.properties
file in src/main/resources. GraphiQL is a small web app for exploring
GraphQL APIs interactively from the browser. Think of it as a REPL
(“Read-Eval-Print Loop”) tool for GraphQL.

Restart your service. You should see a log entry GraphQL endpoint
HTTP POST /graphql if you’re successful. Then open GraphiQL by
navigating to http://localhost:8080/graphiql.

If your service is not starting correctly, double check that your


schema file schema.graphqls is stored in the graphql subfolder inside
resources.

Let’s try our first query. Enter the following on the left-hand side of
the GraphiQL playground:
query myPets {
pets {
name
color
}
}

Then send the query by clicking the play button, a pink button with
a triangle icon, as in the screenshot “GraphiQL request and
response”.

GraphiQL request and response

On the left-hand side of the GraphiQL playground, we see our query


and on the right we see the JSON response. The structure of the
response matches the query exactly.

Take a moment to experiment with GraphiQL. For example, you


could remove color from the query. Try the automatic completion
help when typing a query.
Open the Documentation Explorer by clicking on document icon in
the top left corner, as shown in the screenshot “GraphiQL
Documentation Explorer”.

GraphiQL Documentation Explorer

Then click on the root Query for pets, and then click on Pet to see its
attributes (name and color), as shown in the screenshot “GraphiQL
Pet documentation”.

GraphiQL Pet documentation

Congratulations on completing your first Spring for GraphQL


service! In this chapter, we discussed concepts at a very high level.
In the remainder of this book, we’ll discuss each concept in greater
detail. Later, in the Building a GraphQL service chapter, we’ll extend
this service with more features.

To quickly recap what we did:

Created a new GraphQL service with WebFlux via


https://start.spring.io
Added a GraphQL schema file in SDL format
Implemented a GraphQL controller that returns a list of Pet
data
Executed a GraphQL query

What is GraphQL?
GraphQL is a technology for client-server data exchange. The typical
uses cases are web or mobile clients accessing or changing data on a
backend server, as shown in the diagram “Client Server”. We
sometimes describe the two parties involved as the API “consumer”
and “producer”.

Client Server

In practical terms, GraphQL comprises two different parts: a


domain-specific language that enables the client to specify their
intent such as what data to query, which action to perform, data
attributes to be returned, and a backend service able to execute this
request. The domain-specific language is called the “GraphQL query
language”; although “query” unfortunately is an overloaded term,
since the “query language” lets you perform updates (or
“mutations”), subscriptions, and queries.

For example, a request to query a list of pets and their names:


# This is a comment

# We are asking for two fields:


# "pets" and the "name" for each pet

query myPets {
pets {
name
}
}

This is a “query operation”, but there are also “mutation” and


“subscription” operations that can be executed by a GraphQL
service. We’ll explain operations in more detail in the Query
Language chapter. Assuming there are two pets named Luna and
Skipper, this is the JSON response:
{
"data":
{
"pets": [
{
"name": "Luna"
},
{
"name": "Skipper"
}
]
}
}

This also shows a key feature of GraphQL: we need to explicitly ask


for every piece of information we want. The response contains
exactly the fields we asked for: the “name” of every pet.
If we changed the query and also ask for the color:
query myPets {
pets {
name
color
}
}

the response would look like this:


{
"pets": [
{
"name": "Luna",
"color": "cappuccino"
},
{
"name": "Skipper",
"color": "black"
}
]
}

Technically speaking, we normally send a GraphQL request as an


HTTP POST with the operation specified in the HTTP body with the
response as JSON. Every POST request is sent to the same URL
(typically ending with /graphql), but the body of the POST request
varies based on what you want to request.

The GraphQL ecosystem today offers implementations in nearly


every language. The GraphQL specification (often shortened to
“spec”) maintains consistency across all implementations, which
defines the exact behaviour of each GraphQL request. Beside the
spec, there is a reference implementation written in TypeScript.

One thing to note is that the spec describes how to execute a


GraphQL request should be executed with no considerations of the
transport layer. A GraphQL request (in the abstract spec sense)
could be an in-memory API call or could be a request via RSocket,
the spec describes merely a “GraphQL engine”. This is important for
later to understand for how GraphQL Java and Spring for GraphQL
relate to each other.

While in theory GraphQL could be executed over many transport


protocols, the vast majority of GraphQL APIs use HTTP. Although
the spec does not yet specify GraphQL over HTTP, the GraphQL
community in practice agrees on a GraphQL request over HTTP
standard. In this book, we will also only focus on GraphQL via
HTTP for queries and mutations, and GraphQL via WebSocket for
subscriptions.

A GraphQL API is a statically typed API. “Typed” means that a


GraphQL API contains a clear description of what the consumers
can do with an API. The API doesn’t change often. When the API
does change, it normally involves a redeployment of the service.

For example, the API used by the queries above would look like this:
# This is a comment

# this is the root Object, because it is named Query


type Query {
# a field named "pets" which returns a list of Pet
pets: [Pet]
}

# an Object named Pet


type Pet {
# a field with the name "name" of type String
name: String
color: String
}

This syntax is called Schema Definition Language (SDL) and the


structure of a GraphQL API is called a schema. Every GraphQL API
has a schema that clearly describes the API in SDL syntax. The best
way to think about a GraphQL API for now is that it is a list of types
with a list of fields.
Every GraphQL API offers special fields that let you query the
schema of the API itself. This feature is called introspection. For
example, a valid query for every GraphQL API is this:
query myIntrospection {
__schema {
types {
name
}
}
}

For our API from above, it would return:


{
"__schema": {
"types": [
{
"name": "Query"
},
{
"name": "Pet"
}
]
}
}

As you can see the special field __schema starts with __, which
indicates this is an introspection field, not a normal field.

GraphQL is an API technology

We want to highlight one very important aspect of GraphQL: it is an


API technology that is agnostic to the source of your data. When
implementing a GraphQL service, the actual data can come from
anywhere. GraphQL is not a database or persistence-specific
technology. And while there are technologies and services that let us
expose our database schema as GraphQL API, that is not a robust
architecture when our system will grow: these are two things that
we should not couple together. We highly recommend you think
about a database schema as something different from a GraphQL
schema.

A brief history of GraphQL


The official birthday of GraphQL was the 29th of February 2012,
when it was an internal proposal at Facebook with the name
SuperGraph.

It was part of the effort to rewrite the Facebook iOS client as a


native app and aimed to solve multiple problems the team
encountered with traditional REST like APIs described by co-creator
Lee Byron in this 10-minute video keynote of a Brief History of
GraphQL:

Slow on the network: multiple coordinated round-trips were


required to fetch the needed data.
Fragile client/server relationship: changes to the server API
could easily break the app and docs were often out of date.
Tedious code and process: necessary service changes often
blocked client development.

GraphQL addressed these issues with the features outlined in the


previous section: a query language allows the client to specify
exactly what they want, develop flexibly and independently of the
server, and a static type system that makes the client/server
relationship much more stable.

After GraphQL was successfully used inside Facebook for a few


years, it was open sourced in July 2015. Two artifacts were
published together: the GraphQL spec and the reference
implementation. The reference implementation was initially a
JavaScript implementation of the spec, but it’s now also available in
TypeScript. This dual approach of having a clear spec together with a
reference implementation led to implementations across every
major programming language and ecosystem, including Ruby, PHP,
.NET, Python, Go, and of course Java.

After the open source release in 2015, GraphQL was owned and run
by Facebook until the end of 2018 with the creation of the GraphQL
Foundation. The GraphQL Foundation is a vendor-neutral entity,
comprising over 25 members. The list includes AWS, Airbnb,
Atlassian, Microsoft, IBM, and Shopify. The official description of
the foundation is:

The GraphQL Foundation is a neutral foundation founded by


global technology and application development companies. The
GraphQL Foundation encourages contributions, stewardship,
and a shared investment from a broad group in vendor-neutral
events, documentation, tools, and support for GraphQL.

Legally, the GraphQL Foundation owns the GraphQL trademark and


the copyright for certain GraphQL projects.

The official web page of GraphQL is https://graphql.org. Note that


the domain ending in .com (https://graphql.com) is an unrelated
page owned by a company.

Practically speaking, the Foundation is ultimately responsible for


the official GraphQL projects under the GraphQL GitHub
organization. It includes the spec, the reference implementation,
and GraphiQL. Most of the GraphQL implementations are not part
of the GraphQL Foundation, even though they implement the spec.

The most important group for developing the GraphQL spec is the
GraphQL Working Group (often shortened to WG). It is an open
group that meets online three times a month and mainly discusses
GraphQL spec changes and improvements. Everybody from the
GraphQL community can join. More details are available in the
working group GitHub repository.
From GraphQL Java to Spring for
GraphQL
Shortly after GraphQL was open sourced, a first version of GraphQL
Java was released. One of the fundamental design decisions that I
(Andi) made was to focus purely on the execution part of GraphQL.
GraphQL Java always aimed to be a spec-compliant GraphQL
engine, not a fully-fledged framework for GraphQL services. This
meant that GraphQL Java should never deal with HTTP I/O or any
kind of threading, to the extent that is possible.

While I am still quite happy with this decision, because it allowed


GraphQL Java to have a strong focus and widespread adoption, it
came with one clear downside: every service would need to solve the
HTTP integration itself.

So some time after I released GraphQL Java, the first GraphQL Java
Spring integrations became available. I even developed a small
GraphQL Java Spring library, which aimed to be as lightweight as
possible. But nothing beats an official Spring integration maintained
by the Spring team that allows for the most GraphQL adoption and
best experience overall.

In July 2020, the Spring and GraphQL Java teams came together to
develop an official Spring for GraphQL integration. One year later,
we published a first milestone and after that, the first release of
Spring for GraphQL in May 2022.

Spring for GraphQL aims to be an unopinionated integration of


GraphQL Java into Spring with a focus on comprehensive and wide-
ranging support. It should serve as a fundamental building block for
GraphQL solutions with Spring. Another key design decision was
the direct usage of GraphQL Java itself, there is no abstraction or
additional layer between Spring and GraphQL Java.
Spring for GraphQL comprises two parts: one is the actual Spring
Framework integration with GraphQL Java, and on top of that there
is the Spring Boot Starter for GraphQL. In this book, we will build
services developed with Spring Boot that take advantage of the
Spring Boot GraphQL Starter.

We will constantly move between the GraphQL Java and Spring


world, but it is often important to understand which layer
contributes what part in order to take full advantage of Spring for
GraphQL. This is especially valuable for troubleshooting.
Throughout this book, we’ll make it clear when we talk about a
Spring for GraphQL concept and when we are directly discussing a
GraphQL Java concept.

In the next few chapters, we will explain the concepts used in this
initial service in greater detail, and also discuss core GraphQL
concepts. After that, we will build a more substantial application to
review what we have learned. Later in the book, we’ll cover more
advanced topics.
Overview
In this chapter, we cover the fundamentals aspects of Spring for
GraphQL and GraphQL Java, and how they relate to each other.
This is important to build an overall understanding and not get lost
in the details in the next chapters.

This chapter will cover concepts at a high level to provide an


overview. Later in the book, we’ll explain these concepts in greater
detail.

Three layers
We have three layers to consider, where the higher ones depend on
the lower ones.
Three layers

From bottom to top in the diagram “Three layers”:


GraphQL Specification (Spec): defines in a technology-
independent way what GraphQL is and what a GraphQL service
needs to implement.
GraphQL Java: implements the GraphQL spec for Java.
Spring for GraphQL: integrates GraphQL Java with Spring.

We can also look at this as a hierarchy of abstractions, from less


opinionated at the bottom to more specific at the top:

The spec is the most abstract as it is relevant for all


implementations of GraphQL, not only for Java. It is only a text
document, not running software. For example, it describes what
a GraphQL mutation is, without describing the details of its
implementation.
GraphQL Java implements the spec and actually provides
running software. It is not tied to any specific framework like
Spring and doesn’t deal with any transport level details.
Spring for GraphQL: leverages GraphQL Java to provide a
comprehensive framework for building GraphQL services,
including transport level details.

We can consider GraphQL Java and the spec as the same from a
practical Java point of view. GraphQL Java doesn’t offer major
features beyond the spec and the spec doesn’t define anything that
is not represented in Java, it is a one-to-one relationship. Therefore,
we will not discuss the spec separately from GraphQL Java, and we
can assume features in GraphQL Java by default to be defined in the
spec.

Schema and SDL


A GraphQL API uses static types, so it can only do what it clearly
describes. Changing the API usually involves a redeployment of our
service.
The schema is the description of the API, which is an instance of
GraphQLSchema in GraphQL Java.

The Schema Definition Language (SDL) syntax defines the schema


in a human-readable format.
type Query {
pets: [Pet]
}

type Pet {
name: String
color: String
}

This is a schema in SDL format defining two types: Query and Pet.
The Query type has a pets field. The Pet type has two fields, name and
color.

The SDL format is great for defining a schema, and makes the
schema easily readable. During execution, GraphQL Java uses an
instance of GraphQLSchema, which represents the provided SDL
schema.

We will discuss GraphQL schemas in depth in the Schema chapter.

GraphQL query language


The GraphQL query language is a domain-specific language for
describing what a client wants to request. It looks similar to JSON
on purpose, but it is not JSON.
query myPets {
pets {
name
}
}
The client is required to explicitly “select” any data it wants, such as
the names of pets. The response is in JSON, and only contains the
data requested, no more and no less.
{
"data":{
"pets": [
{
"name": "Luna"
},
{
"name": "Skipper"
}
]
}
}

A response can also contain another two top level keys, “errors” and
“extensions”. We’ll discuss this in more detail in the Request and
Response chapter.

Request and Response


Every interaction of a consumer of a GraphQL API starts with a
GraphQL request, and results in a GraphQL response.

A GraphQL request in GraphQL Java is an instance of ExecutionInput


and the response is an ExecutionResult. These are request and
response definitions independent of a specific transport level. From
a GraphQL Java perspective, a request and a response are just an
argument and return value of a method call. We’ll discuss GraphQL
requests and responses in more detail in a dedicated chapter later in
the book.

Spring for GraphQL defines a GraphQlRequest with the most


important implementation being WebGraphQlRequest for requests via
HTTP or WebSocket. WebGraphQlRequest contains specific data for the
HTTP request, such as URL and HTTP headers.

A response in Spring for GraphQL is a GraphQlResponse with


WebGraphQlResponse again being specific to HTTP or WebSocket,
containing the HTTP response headers.

Most notably, a GraphQL request contains a GraphQL document,


which is text in GraphQL query language format that contains the
intent of the client.

The response can contain data, errors, and extensions. Data can be
anything. In ExecutionResult, data is Map<String, Object>. On the
transport layer, we send the response over HTTP in JSON.

Execution and DataFetcher


We can roughly divide the actual execution of a GraphQL request
via HTTP like this:

1. First, the request is deserialized and processed (Spring).


2. We invoke GraphQL Java, which involves fetching data
(GraphQL Java).
3. Then we create and serialize the response (Spring).

The first step is purely Spring and can also involve aspects like
authentication. Then Spring invokes GraphQL Java and once
finished, the response is again handled by Spring and sent back to
the client.

Step two involves multiple steps, such as invoking the relevant


DataFetchers that retrieve the data necessary to fulfill the request.
We’ll go into much more detail on DataFetchers in a dedicated
chapter later in this book.
In Spring for GraphQL, annotated controller methods are registered
as DataFetchers. For example, in the previous chapter we built a
sample service with a list of pets:
package myservice. service;

import org. springframework. graphql. data. method. annotation


.QueryMapping;
import org. springframework. stereotype. Controller;

import java. util. List;

@Controller
class PetsController {

@QueryMapping
List< Pet> pets() {
return List.of(
new Pet( "Luna", "cappuccino"),
new Pet( "Skipper", "black"));
}

The @QueryMapping annotation registers this method as a DataFetcher


for a field pets in the type Query, connecting the method to the field
of the same name. We’ll explain Spring for GraphQL’s schema
mapping annotations in detail in the DataFetchers chapter.

The last step of creating and serializing the response is handled by


Spring for GraphQL.

For more on execution, see the dedicated chapter later in this book.
How concepts relate to each other

Concept relations

As shown in the diagram “Concept relations”,

The SDL defines the schema


A request contains text in query language format
The execution of a request uses the schema and DataFetchers to
produce a response

GraphQL Java
The primary classes of GraphQL Java are, as shown in the diagram
“GraphQL Java classes”:

graphql.GraphQL : Normally only one instance per service. Used to


start GraphQL Java execution.
graphql.ExecutionInput : A GraphQL Java request.

graphql.ExecutionResult : A GraphQL Java response.

graphql.schema.GraphQLSchema : Description of the GraphQL API


plus the logic needed to execute it.

graphql.schema.DataFetcher : Java interface to be implemented by


every service. Your GraphQL service will implement DataFetchers
via Spring for GraphQL’s schema mapping annotations.

GraphQL Java classes

Spring for GraphQL


The primary classes of Spring for GraphQL are (all class name start
with org.springframework.graphql, abbreviated as o.s.g):

o.s.g.server.webflux.GraphQlHttpHandler : WebFlux handler.


Creates WebGraphQlRequest and calls a WebGraphQlHandler

o.s.g.server.webmvc.GraphQlHttpHandler : Spring MVC handler.


Creates WebGraphQlRequest and calls a WebGraphQlHandler
o.s.g.server.WebGraphQlRequest : A GraphQL request over HTTP or
WebSocket.

o.s.g.server.WebGraphQlResponse : A GraphQL response over HTTP


or WebSocket.

o.s.g.server.WebGraphQlHandler : Handles GraphQL requests over


HTTP or WebSocket.

o.s.g.server.WebGraphQlInterceptor : Hook to intercept a


WebGraphQlRequest and WebGraphQlResponse.

o.s.g.graphql.ExecutionGraphQlService : The actual service


responsible for invoking GraphQL Java.

A request passes through three primary classes in Spring for


GraphQL, each with a distinct responsibility, as shown in the
diagram “Spring for GraphQL classes”:

1. A general purpose HTTP request invokes GraphQlHttpHandler


converts the request into a WebGraphQlRequest.
2. WebGraphQlHandler takes the WebGraphQlRequest, and calls
ExecutionGraphQlService to execute the request.
3. ExecutionGraphQlService ultimately invokes GraphQL Java.

Spring for GraphQL classes


The most relevant of these steps in daily usage is the second one as
the WebGraphQlHandler offers the ability to intercept any request and
response via WebGraphQlInterceptor.

A quick note on capitalization. You might have noticed the


capitalization of the letters “Q” and “L” in class names vary. In all
Spring for GraphQL classes, “Q” is always in upper case and “l” is
always in lower case, to be consistent with Spring naming
conventions. In GraphQL Java, names usually include “Q” and “L”
in upper case. Where this convention has not been followed, we’ll
call it out in the relevant code example.

This chapter summarized the most fundamental concepts at a high


level. Throughout this book, we’ll expand on all these topics is
greater detail.
Schema
The schema of a GraphQL API is the static description of the API. A
schema describes what a consumer of the API may request, which
can be represented in Schema Definition Language (SDL) format
(also called “SDL syntax” or “SDL notation”).

Under the hood, Spring for GraphQL represents an executable


schema with GraphQL Java’s GraphQLSchema, which is then used to
create a GraphQL object. It is an “executable” schema because it
contains all the logic needed to execute a request, as well as the
description of the API for the consumer. In this chapter, we will
focus on the API description. In the DataFetchers chapter, we will
discuss how this API description connects to the execution logic.

Schema-first
“Schema-first” refers to the idea that the design of a GraphQL
schema should be done on its own, and should not be generated or
inferred from something else. The schema should not be generated
from a database schema, Java domain classes, nor a REST API.

Schemas ought to be schema-first because they should be created in


a deliberate way and not merely generated. Although a GraphQL
API will have much in common with the database schema or REST
API being used to fetch data, the schema should still be deliberately
constructed.
We strongly believe that this is the only viable approach for any
real-life GraphQL API and we will only focus on this approach. Both
Spring for GraphQL and GraphQL Java only support “schema-first”.

In GraphQL Java, the schema is represented by an instance of


GraphQLSchema. This can be created either via SDL or
programmatically. Both approaches are “schema-first” because the
schema is deliberately designed. In this book, all examples will use
schemas created via SDL.

Loading schema resources in Spring for


GraphQL
Spring for GraphQL automatically loads schema files with
extensions .graphqls or .gqls in the directory
src/main/resources/graphql. All you need to do is save the schema file
in the correct location. You can alternatively load schema files from
a different location, see details in the documentation.

Under the hood, Spring for GraphQL automatically reads the


schema files, parses the files, and instantiates GraphQL Java’s
GraphQLSchema object with the schema.

GraphQL schema elements


A schema in GraphQL Java is represented as a GraphQLSchema
instance, which is a graph of GraphQLSchemaElements. We will describe
the most important elements of a GraphQLSchema and the notation in
SDL format.

GraphQL types
The most important schema elements are the types. There are eight
types in the GraphQL type system: Object, Interface, Union, Enum,
Scalar, InputObject, List, and NonNull. The first six are “named
types”, because each type has a unique name across the whole
schema, while List and NonNull are called “wrapping types”,
because they wrap named types, as we will see later.

Another classification of types differentiates between input and


output types. An output type is a type that describes the result of a
request, while input types are used to describe input data for a
GraphQL request. We will cover requests in greater detail in the
next chapter on GraphQL query language.

Input and Output types

Fields everywhere
The most prominent elements of a schema are fields. Objects and
interfaces contain fields which can have arguments. An input object
has input fields, which cannot have arguments. If we squint, we can
think of a schema as a list of types with a list of fields.

In GraphQL Java, a field is represented by an instance of


GraphQLFieldDefinition for object and interface fields, or
GraphQLInputObjectField for input objects.

Scalar
A scalar is a primitive type describing a certain set of allowed values.
For example, a Boolean scalar means the value can be true or false,
an Int can be any number between -2^31 and 2^31, and a String can
be any String literal. A scalar name must be unique across the
schema.

GraphQL comes with five built-in scalars: String, Int, Float, Boolean,
and ID. In addition, every GraphQL service can define its own
custom scalars.

In SDL, custom scalars need to be declared explicitly, while built-in


scalars can be used without any declaration.
"""
A custom scalar representing a date without any time zone.
Note how this custom scalar must be declared explicitly.
"""
scalar Date @specifiedBy(url:
"https://scalars.graphql.org/andimarek/local-date")

type Pet {
"String is a built-in scalar, therefore no declaration is requir
name: String
dateOfBirth: Date
}
The built-in @specifiedBy directive links to a custom scalar
specification URL. We’ll discuss this in more detail in the Directives
chapter. The @specifiedBy directive is optional, but is highly
recommended.

Enum
An enum type describes a list of possible values. It can be used as an
input or output type. Enums and scalars are the primitive types of
the GraphQL type system. An enum name must be unique across
the schema.

Here is how to declare an enum in SDL:


enum PetKind {
CAT, DOG, BIRD
}

type Pet {
name: String
kind: PetKind # used as output type
}

type Query {
pets(kind: PetKind!): [Pet] # used as input type
}

Alternatively, enums can also be declared with each value on its


own line. This is because a comma , is considered whitespace and is
ignored.
enum PetKind {
CAT
DOG
BIRD
}
In GraphQL Java, an enum is represented by an instance of
GraphQLEnumType.

Object
A GraphQL object type describes a certain shape of data as a list of
fields. It has a unique name across the schema. Each field has a
specific type, which must be an output type. Every field has an
optional list of arguments. Recursive references are allowed.

An object type is declared in SDL as type.


type Pet {
name: String
color: String
friend: Pet # recursive reference
owners: [Person!] # A list of people
}

type Person {
name: String
}

type Query {
pet(name: String!): Pet # lookup a pet via name
}

In GraphQL Java, an object type is represented by an instance of


GraphQLObjectType with GraphQLFieldDefinition defining its fields.

Input object
An input object type describes a group of input fields where each
has an input type. An input object name must be unique across the
schema.
In SDL, an input object is declared via input.
input PetFilter {
minAge: Int
maxAge: Int
}

type Pet {
name: String
age: Int
}

type Query {
pets(filter: PetFilter): [Pets]
}

Interface
Similar to an object, an interface describes a certain shape of data as
a list of fields and has a name. In contrast to an object, an interface
can be implemented by another interface or object. An interface or
object implementing an interface must contain at least the same
fields defined in the interface. In that sense, an interface is used to
describe an abstract shape, which can be realized by different
objects.

Similar to objects, every field has an optional list of arguments.


Every argument has a name and type, which must be an input type.

An interface in SDL is declared as interface. Implementations by


other interfaces or objects specify implements followed by the
interface name. In the example below, both Dog and Cat types
implements the Pet interface. Both Dog and Cat types contain at
least fields for name and owners, which are declared in the Pet
interface.
interface Pet {
name: String
owners(includePreviousOwners: Boolean): [Person!]
}

# One implementation of the interface


type Dog implements Pet {
name: String
owners(includePreviousOwners: Boolean): [Person!]
doesBark: Boolean # additional field specific to Dog
}

# Another implementation
type Cat implements Pet {
name: String
owners(includePreviousOwners: Boolean): [Person!]
doesMeow: Boolean # additional field specific to Cat
}

It might surprise you that all interface fields must be repeated. As


we can see in this example, Cat and Dog both repeat the same name
field from Pet. This was a deliberate decision by the GraphQL
working group to focus more on readability than shorter notation.

In GraphQL Java, an interface is represented as an instance of


GraphQLInterfaceType, and fields by instances of
GraphQLFieldDefinition.

Union
A union type must be one of the member types at execution time. In
other words, a union is fully described by the list of possible object
types it can be at execution time.

In SDL, a union is declared as a list of object types separated by a


vertical bar |.
union Pet = Dog | Cat

type Dog {
name: String
doesBark: Boolean
}

type Cat {
name: String
doesMeow: Boolean
}

In GraphQL Java, a union is represented as an instance of


GraphQLUnionType.

List and NonNull


Wrapping types contain another type, which can be another
wrapping type or named type. Ultimately, a wrapping type wraps a
named type.

A list type is a list of the wrapped type. A non-null type marks this
type as never being null.

A wrapping type can be used as a type for a field argument, as a field


or input field type.

In SDL notation, a non-null type is declared by appending an


exclamation mark ! to the type. A list is declared by surrounding the
type with brackets [ ].
type Query {
pet(id: ID!): Pet
}

type Pet {
id: ID!
ownerNames: [String!] # A combination: a list of non-null string
}
GraphQL Java ensures that any field or input field marked as non-
null is never null. In GraphQL Java, list types are represented by
instances of GraphQLList and non-null types by instances of
GraphQLNonNull.

Directives
A directive is a schema element that allows us to define metadata
for a schema or a GraphQL operation. A directive needs to declare
all possible locations where it can be used. A directive contains an
optional list of arguments, similarly to fields.

An instance of a directive is called an applied directive. We’ll discuss


applied directives in more detail in the Directives chapter.

To declare a directive in SDL:


# This is a directive without any argument
# and it can only used in two locations inside the schema
directive @example on FIELD_DEFINITION | ARGUMENT_DEFINITION

# Example usage
type SomeType {
field(arg: Int @example): String @example
}

In GraphQL Java, a directive declared in the schema is represented


as GraphQLDirective.

There’s much more to directives. We will dive into greater detail in


the Directives chapter.

Arguments
Directives, object type fields, and interface fields can have an
optional list of arguments.

Every argument has a name and type, which must be an input type.
In the following example, the pet field has one defined argument
called name which is of type String!.
type Query {
pet(name: String!): Pet # lookup a pet via name
}

type Pet {
name: String
color: String
}

A default value can be optionally defined with the equals sign =. In


the following example, you can fetch a specific number of pets.
Alternatively, if the number of pets is not specified, it will default to
20.
type Query {
pets( howMany: Int = 20): [ Pet]
}

Arguments can be optional or required. An argument is required if


the argument type is non-null and does not have a default value.
Otherwise, the argument is optional. For example, the argument
name: String! is required.

Documentation with descriptions


Documentation is a powerful feature of GraphQL. Descriptions of
GraphQL definitions such as fields and types are embedded in the
schema, alongside their definitions. These descriptions are made
available via introspection, and can be used to generate interactive
documentation.
Nearly every SDL element can have a description in Markdown
format as documentation. The description is either a string literal
enclosed in quotation marks ", or a block string wrapped in triple-
quotes """ for multi-line documentation with line breaks or
additional Markdown.
type Query {
"all currently known pets"
pets: [Pet]
}

"""
A Pet can be a Dog or or Cat.
A Pet has a human owner.
"""
type Pet {
name: String
owner: Person
}

These descriptions are collated into documentation, for example in


the GraphiQL playground in the screenshot “Documentation in
GraphiQL”.

Documentation in GraphiQL

To access documentation in GraphiQL, click on the book icon in the


top left corner. See the Introduction chapter for how to add
GraphiQL to your Spring for GraphQL application.
The GraphQL specification recommends that the schema and all
other definitions (including types, fields, and arguments) should
provide a description unless they are considered self-descriptive.

Comments
Comments start with a hash sign # and everything on the same line
is considered a part of a comment. For example:
type Query {
# This is a comment
hello: String
}

# Multi line comment


# requires
# multiple hash signs

Note that comments are very different to descriptions. Descriptions


are used to construct documentation for the schema, which is made
available via introspection. Comments are ignored like whitespace,
and are not used in documentation.

In this chapter, we discussed the schema elements that describe the


structure of an API. In the DataFetchers chapter, we’ll discuss how
this API description connects to the logic to execute requests.
Before we get to DataFetchers, let’s discuss the GraphQL query
language.
GraphQL query language
The GraphQL query language is the domain-specific language (DSL)
that enables consumers define what they would like to do.

Note that the phrase “query language” encapsulates queries,


mutations, and subscriptions, as we’ll discuss in this chapter.

The query language syntax is deeply related to the GraphQL


schema. A GraphQL schema defines which queries, mutations, or
subscriptions are valid in query language. In the query language
examples below, assume there is a corresponding GraphQL schema
that makes the query language valid.

Literals
The query language contains several literals that mirror the schema
input types.

String literals written as "String literal", enclosed with double


quotation marks
Boolean literals which are true or false
Int literals written as 123
Float literals written as 123.45
A literal with no value (Null) written as null
List literals written as ["one list element", "another list
element"], wrapped with square brackets
Enum values written as ENUM_VALUE
Input object literals written as { someField: "value", anotherOne:
{ fieldInField: 123 } }. They represent an unordered list of key
input, wrapped in curly braces

Operations
There are three operations in GraphQL:

query, a read-only fetch


mutation, a write followed by a fetch
subscription, a long-lived request that fetches data in response
to events

In a GraphQL schema, these operations are modelled as root


operation types.

In GraphQL query language, these operations also form the root of


the query.

Query operations
A query operation requests data and returns a response containing
the result.

A simple query called myQuery, fetching a single field someField, is


written like this:
query myQuery {
someField
}

A query operation is a tree of selected fields. The fields on the first


level of the query are called root fields. The fields below another
field are a sub-selection. Every selected field in query operation
must match their respective schema definitions.
In this example operation, pet is a root field, and name is a sub-
selection.
query petName {
pet {
name
}
}

Selected fields in this petName query correspond to their respective


schema definitions:
type Query {
pet: Pet
}

type Pet {
name: String
}

A key feature of GraphQL is that only selected fields are returned,


no more and no less. To enable this feature, fields of object,
interface, and union types require a sub-selection. Every field must
be explicitly selected, there are no wildcard selections. Requiring
sub-selections was a deliberate decision in the GraphQL spec to
ensure queries are predictable and therefore clients always receive
exactly what they ask for.

As a more complex example, a query can request the country of the


address of the owner of a pet.
query petOwnerDetails {
pet {
name
owner {
name
address {
country
}
}
}
}

Queries are always validated against the schema of the API. We


cannot query fields that are not defined in the schema.

For this schema:


type Query {
pet: Pet
}

type Pet {
name: String
}

We will not be able to execute the following invalid query. An error


will be raised because there is no nickName field on the Pet type.
query invalid {
pet {
name
nickName
}
}

Although there is a difference between a “field definition” in schema


and a “field” used in query language, we often use the word field for
both if the context is clear.

Mutation operations
A mutation operation is a write followed by a fetch. Mutations
should have a side effect, which usually means changing (or
“mutating”) some data.

Declare a mutation operation with the keyword mutation followed by


the name of the mutation:
mutation myMutation {
changeSomething
}

Every root field of a mutation operation must be a field of the


Mutation object type in the schema. Only the root fields of a
mutation can have side effects, as required by the GraphQL spec.

Sub-selections of the root field of a mutation operation are


semantically equal to queries. In practice, it’s useful for a mutation
to return some of the changed data. For example, after updating the
name of a User, it’s useful to receive the newly changed User in the
response.

For example, in the schema, the changeUser field is defined in the


Mutation type:

type Mutation {
changeUser(newName: String!): User
}

type User {
name: String
address: Address
}

type Address {
street: String
country: String
}

After changing the user’s name to “Brad”, we can query the details
of the changed user as a normal query. Notice how the sub-selection
looks exactly like a query sub-selection.
mutation changeUserName {
changeUser(newName: "Brad") {
name
address {
street
country
}
}
}

If there are two or more root fields in the mutation operation, they
will be executed in sequence, as required by the GraphQL spec.
mutation mutationWithTwoRootFields {
first: changeName(name: "Bradley")
second: changeName(name: "Brad")
}

In this example, the final name of the user will always be “Brad”,
because the fields are always executed in sequence. The second
name change to “Brad” will always be executed last.

Subscription operations
A subscription is a long-lived request that sends updates to the
client when new events happen.

For example, if the client wants to be informed about every new


email matching certain criteria:
subscription newMessage {
newEmail(criteria: {
sender: "[email protected]",
contains: "playing" }
) {
text
}
}

A subscription can only contain exactly one root field, like newEmail.
This is in contrast to query and mutation operations, which can
contain many root fields.

The execution of a subscription and the handling of the request on


the transport layer differs significantly from queries and mutations.
This is simply because a long-lived subscription request reacting to
certain events is more complicated than a simple process of query
(or mutation) request, execution, and response. We’ll discuss this
further in the Subscriptions chapter.

Arguments
Fields can have arguments, which have their type defined in the
schema. Arguments can be either optional or required. As discussed
in the Schema chapter, an argument is required if it is non-null
(indicated with !) and there is no default value (declared with =).

For example, a schema defining a pet field with a required id


argument:
type Query {
pet(id: ID!): Pet
}

type Pet {
name: String
}

In a query, this is how to request a pet with the string literal “123” as
its id value.
query petSearch {
pet(id: "123") {
name
}
}

Arguments can be any literal, including null, as long as it conforms


to the schema. For example, here’s a query with an input object
literal.
query petAgeSearch {
pets(filter: { minAge: 10, maxAge: 20 }) {
name
}
}

Argument types in operations must correspond to their respective


schema definitions. If the schema requires an Int argument value,
and we provide a Boolean in a query, the incorrect argument will
cause a validation error.

Fragments
Fragments allow us to reuse parts of the query. Fragments are a list
of selected fields (including sub-selections) for a certain type.
Fragments have a name and type condition, which must be an
object, interface, or union.

Fragments are declared outside of operations. The fragment


definition syntax is:
fragment <FragmentName> on <TypeCondition> {
<fields>
}

Use fragments inside operations with the fragment spread syntax:


... <FragmentName>

For example, let’s define a personDetails fragment, to reuse common


repeated fields of Person. Let’s use the fragment in the petOwners
query.
fragment personDetails on Person {
firstName
lastName
title
}

query petOwners {
pets {
owner {
...personDetails
}
previousOwner {
...personDetails
}
}
}

In this example, we use personDetails twice, for both the owner and
previousOwner fields.

Inline fragments
Inline fragments are a selection of fields with a type condition.
Inline fragments are used to query different fields depending on the
type. You can think of them as switch statements, that depend on
the type of the previous field.

Inline fragments are different to fragments, as they are declared


inline rather than outside an operation. Unlike fragments, inline
fragments have no name, and cannot be reused.

The inline fragment syntax is:


... on <TypeCondition> {
<fields>
}

Consider this schema:


type Query {
pets: [Pet]
}

interface Pet {
name: String
}
type Dog implements Pet {
name: String
doesBark: Boolean
}

type Cat implements Pet {


name: String
doesMeow: Boolean
}

We can write inline fragments to query the doesBark field for Dog
results and doesMeow for Cat results.
query allThePets {
pets {
... on Dog {
doesBark
}
... on Cat {
doesMeow
}
}
}

Depending on the Pet type, we select different fields. We are only


interested in doesBark for Dogs, while for Cats we are only interested
in doesMeow.

Types implementing interfaces will likely add additional fields


which are not shared across all implementations. For example, the
doesBark field only appears in the Dog type. Fragments or inline
fragments must be used to query fields which are not guaranteed
across all implementations.

As a counterexample, the query below is invalid because there is no


doesBark field in the Pet interface.

query invalid {
pets {
doesBark
}
}

Fragments or inline fragments must be used for union types. A


GraphQL union represents an object that could be one of a list of
types, but a union does not define any fields itself.

For example, consider this example schema with two important


food groups:
union Food = Pizza | IceCream

type Query {
dinner: [Food]
}

type Pizza {
name: String
toppings: [String]
}

type IceCream {
name: String
flavors: [String]
}

A query for dinner must include fragments or inline fragments,


because the union does not define any fields itself.
query healthyDinner {
dinner {
... on Pizza {
name
toppings
}
... on IceCream {
name
flavors
}
}
}
The following query is invalid, because the union type Food does not
define any fields.
query invalid {
dinner {
toppings
flavors
}
}

Variables
We can include parameter variables in a GraphQL operation, which
enables clients to reuse operations without needing to dynamically
rebuild them.

An operation can declare variables that serve as input for the entire
operation.

To declare variables, define them after the operation name. The


variable name begins with $, and is followed by the type. Variables
can be marked as non-null with an exclamation mark !, similarly to
field or directive arguments.

For example, the variable petId is of type ID, and it is non-nullable.


query findPet($petId: ID!) {
pet(id: $petId) {
name
}
}

A default value can be provided, defined after an equals sign =.


query findPet2($petId: ID! = "12345DefaultId") {
pet(id: $petId) {
name
}
}
It’s possible to have multiple variables. In this example mutation,
the first variable is required and the second is not required.
mutation changePetName($petId: ID!, $petName: String) {
changePetName(id: $petId, name: $petName) {
success
}
}

Variable values are sent alongside the operation. For example, we


want to find a pet with ID 9000.
query findPet($petId: ID!) {
pet(id: $petId) {
name
}
}

If providing variable values as JSON, to find a pet with ID 9000, we


would send:
{
"petId": "9000"
}

Aliases
By default, a key in the response object will be set as the
corresponding field name in the operation. Aliases enable renaming
of keys in the response.

Define an alias with the name, followed by a colon:


query aliases {
alias1: someField
alias2: someField
}
Although simple renames are handy, aliases are more often used to
query the same field multiple times with different arguments. As
the response key is set to the field name by default, aliases are
essential if we want to use the same field twice. For example:
type Query {
search(filter: String!): String
}

query searches {
search1: search(filter: "Foo")
search2: search(filter: "Bar")
}

This query will produce a response including the data:


{
"search1": "Foo result",
"search2": "Bar result"
}

GraphQL document
A GraphQL executable document (often shortened to document) is
a text written in GraphQL query language notation that contains at
least one query, mutation, or subscription operation and an optional
list of fragments.

Named and unnamed operations


The operation name is the word following the query, mutation, or
subscription keyword. In the following example, the operation name
is findPet.
query findPet($petId: ID!) {
pet(id: $petId) {
name
}
}

In the GraphQL document, if there are multiple operations, they


must all have a name. If there is only one operation, it can be
unnamed.

Although the specification permits unnamed operations, we


strongly suggest using operation names. They give a meaningful
name that explains the intention of an operation. Operation names
are invaluable for observability in a production GraphQL service.
For example, operation naming helps track query usage over time.
You could also ask consumers to use operation names that identify
the caller, so it’s easier to track who is using the query.

Nevertheless, we will briefly cover unnamed operations, as they do


appear in documentation examples and elsewhere.

If there is only one operation, it can be unnamed. For example this


unnamed mutation:
mutation {
changeName(name: "Foo")
}

Where the single unnamed operation is a query, the keyword query


can be dropped. The two following examples are equivalent:
query {
hello
}

{
hello
}

Where there are multiple operations in a document, they must all


be named. Here’s an example document with a fragment:
query hello {
...helloFragment
}

mutation changeName {
changeName(name: "Foo")
}

subscription nameChanged {
nameChanged
}

fragment helloFragment on Query {


hello
}

Query language in GraphQL Java


In GraphQL Java, a query language text is parsed and transformed
into an abstract syntax tree (AST) of graphql.language.Node instances.
For example, a field is a graphql.language.Field and an inline
fragment is a graphql.language.InlineFragment.

GraphQL Java takes care of parsing and validating the request,


including the query language text, so normally we do not require
direct access to Node instances. We won’t go into further depth on
this topic as it’s an engine detail and not relevant for implementing
a GraphQL service.

In this chapter, we covered GraphQL query language and the three


operations, query, mutation, and subscriptions. In the next chapter
we’ll discuss DataFetchers, the methods which populate data for
fields in a schema.
DataFetchers
A DataFetcher loads data for exactly one field. It’s the most
important concept for executing a GraphQL request, because it’s the
logic that connects your schema and your data.

In the first half of this chapter, we will show how to add your data
fetching logic to Spring for GraphQL via controller annotations.
These controller annotations automate much of the work with
DataFetchers, to the point that even the word DataFetcher does not
appear in controller code. In the second half of this chapter, we will
remove the Spring “magic” and take a look under the hood at how
DataFetchers are used by the GraphQL Java engine. By the end of
this chapter, you will have a thorough understanding of
DataFetchers. We will cover more advanced topics in the
DataFetchers in depth chapter and take a deep dive into execution
in the Execution chapter.

A quick note: DataFetchers are called Resolvers in the GraphQL


specification and in other implementations. I (Andi) named it
DataFetcher because I thought it reflected the purpose better. I am
not convinced I would make the same decision today, but now it is
too late to change.

A GraphQL operation is basically a tree of fields. The execution is


field-oriented: for each field, GraphQL Java loads the data.
However, given that GraphQL is agnostic about where the data
comes from, we must tell GraphQL Java how to load the data.
GraphQL Java needs to know how to load the data for every field,
therefore every field has an associated DataFetcher.

This field-oriented data loading approach differs from REST, where


we implement the logic per endpoint for resources. In GraphQL,
there is only one endpoint and data loading happens per field
depending on the request. This carries over to Spring controllers,
where we map every field DataFetcher to a method in a Controller.
This means that Spring for GraphQL controller methods represent a
GraphQL field, instead of a REST resource.

Although every field has an associated DataFetcher, in practice you


don’t have to manually write a DataFetcher for every field. In the
PropertyDataFetcher section later in this chapter, we’ll show how
most fields can be automatically mapped to DataFetchers.

Spring for GraphQL annotated methods


Spring for GraphQL provides an annotation-based programming
model, where annotated methods are registered as DataFetchers. In
the second half of this chapter, we’ll take a closer look at the
DataFetcher interface inside the GraphQL Java engine. For now,
we’ll see how Spring for GraphQL controller annotations achieve
exactly the same result with far less boilerplate code.

Before we go on, there are a few key classes which work closely with
DataFetchers. In GraphQL Java, a GraphQLSchema is both the
structure (or shape) of the API, and all the logic needed to execute
requests against it. Another key GraphQL Java concept is
RuntimeWiring, which contains GraphQLCodeRegistry, a map of schema
fields to DataFetchers. Each DataFetcher needs to be registered in
the GraphQLCodeRegistry inside RuntimeWiring.

Annotated controller methods enable us to implement a


DataFetcher and also automatically register into the
GraphQLCodeRegistry inside RuntimeWiring. While the Spring Boot
starter automatically initializes and manages RuntimeWiring for
GraphQL Java, you also have the full flexibility to manually access
and modify it. Later in this chapter, we’ll see how to modify
RuntimeWiring when we demonstrate how to add a custom scalar.

There are four different schema mapping annotations available in


Spring for GraphQL: the general @SchemaMapping, and three shortcut
annotations, @QueryMapping, @MutationMapping, and
@SubscriptionMapping.

@QueryMapping is a shortcut annotation for the DataFetcher for the


query type, @MutationMapping and @SubscriptionMapping are for
mutation and subscription types respectively. All three are shortcuts
for the general @SchemaMapping annotation, which takes arguments
typeName and field, to indicate which type and field the DataFetcher
should be mapped to.

Let’s see these annotations in an example. Here is a Pet schema:


type Query {
favoritePet: Pet
}

type Pet {
name: String
owner: Person
}

type Person {
firstName: String
lastName: String
}

With Pet and Owner record classes:


package myservice. service;

record Pet( String name, String ownerId) {


}
package myservice. service;

record Person( String id, String firstName, String lastName) {


}

This is how to register the two DataFetchers with Spring for


GraphQL’s controller annotations.
package myservice. service;

import org. springframework. graphql. data. method. annotation


.QueryMapping;
import org. springframework. graphql. data. method. annotation
.SchemaMapping;
import org. springframework. stereotype. Controller;

@Controller
record PetsController( PetService petService) {

@QueryMapping
Pet favoritePet() {
return petService.getFavoritePet();
}

@SchemaMapping( typeName = "Pet", field = "owner")


Person owner( Pet pet) {
return petService.getPerson( pet.ownerId());
}

The favoritePet method is annotated with @QueryMapping, indicating


it will be registered to a field in the Query type. By default, Spring for
GraphQL will determine the field name from the method name.
This means the favoritePet method will be registered as a
DataFetcher for the field favoritePet in the type Query.

The @QueryMapping annotation is a shortcut. You can alternatively


specify a field with the @QueryMapping annotation, or use the general
@SchemaMapping annotation.
The owner method is annotated with the general @SchemaMapping
annotation. The annotation’s attributes indicate this method will be
registered as a DataFetcher for the owner field in the Pet type.

In addition to registering DataFetchers automatically, these


annotated methods allow for convenient access to different inputs.
In the example above, the owner DataFetcher takes a parameter Pet
pet, which contains the source or parent object Pet, which is
necessary to determine the owner’s name.

In the @SchemaMapping annotation in our example, we can take an


extra shortcut and remove the typeName and field attributes. When
these attributes are not provided, Spring for GraphQL will register
the typeName as the simple class name of the source or parent object
injected into the method (Pet), and the field name will default to
the name of the method (owner).

You might be wondering why only two DataFetchers are registered


in this controller, considering there were 5 fields in the schema.
We’ll see how the remaining fields were automatically wired with
DataFetchers in the PropertyDataFetcher section up next in this
chapter.

PropertyDataFetchers in Spring for


GraphQL
We stated that every field has an associated DataFetcher.
However, in the Pet example earlier, we had five fields in the
schema and only implemented two DataFetchers with controller
annotations. This was not a mistake, and we have not forgotten
anything! This is actually a realistic scenario.

While every field has a DataFetcher, we only need to implement


a few DataFetchers ourselves. The rest are default DataFetchers
that GraphQL Java automatically generates, which are called
PropertyDataFetchers. This is illustrated in the diagram “DataFetcher
per field”.

DataFetcher per field

A PropertyDataFetcher is the perfect choice when the schema


matches the Java object returned by the parent
DataFetcher.

Returning to our Pet example is the best way to understand this: we


have a PropertyDataFetcher for 3 different fields: Pet.name,
Person.firstName, and Person.lastName.

The Pet record class returned by the PetService is:


package myservice. service;

record Pet( String name, String ownerId) {


}
And this is the Person record class:
package myservice. service;

record Person( String id, String firstName, String lastName) {


}

The parent field of Pet.name is Query.favoritePet and our DataFetcher


favoritePet returns the Java object Pet containing the property name
exposed via the standard Java getter convention, so the schema
GraphQL field name matches exactly the Java property. The same is
true for Person: the second DataFetcher owner returns a Person Java
object with a structure identical to the schema: both contain two
strings named firstName and lastName.

A PropertyDataFetcher fetches the data from a Java object as


long as the GraphQL field matches the Java object
property. In our example, it means the Java property follows the
Java getter naming convention.

The PropertyDataFetcher can also fetch data from Java fields, is


compatible with record classes, and can also access values from a
java.util.Map.

The Map support is particularly useful to quickly mock DataFetchers.


For example, our owner DataFetcher can return the same Person
using a Map.
@SchemaMapping
Map< String, String> owner( Pet pet) {
return Map.of( "firstName", "Andi",
"lastName", "Marek");
}

No other changes are required. The keys of the Map match the
schema fields firstName and lastName, so the PropertyDataFetcher will
load the correct values.
DataFetchers and schema mapping
handler methods
A quick note on naming. @SchemaMapping and shortcut annotations
@QueryMapping, @MutationMapping, and @SubscriptionMapping, declare
schema mapping handler methods, which are then registered as
DataFetchers by Spring for GraphQL. There is a one-to-one
relationship between a schema mapping handler method and a
DataFetcher. Therefore, in the remainder of this book, we’ll use the
word DataFetcher for these schema mapping handler methods.

Technically speaking, the schema mapping handler methods do not


implement the DataFetcher interface directly. It is the Spring for
GraphQL AnnotatedControllerConfigurer that registers these handler
methods as DataFetchers. However, as these concepts map one-to-
one, we want you to think of these schema mapping handler
methods as DataFetchers.

TypeResolver in Spring for GraphQL


If the type of the field is an interface or union, GraphQL Java needs
to determine the actual object type of the value via a TypeResolver.
For an introduction to interfaces and unions, see the earlier Schema
chapter.

For example, let’s consider a Pet interface, which is implemented by


Cat and Dog types.
type Query {
favoritePet: Pet
}

interface Pet {
name: String
owner: Person
}

type Dog implements Pet {


name: String
owner: Person
doesBark: Boolean
}

type Cat implements Pet {


name: String
owner: Person
doesMeow: Boolean
}

type Person {
firstName: String
lastName: String
}

Most likely, you will not need to write your own TypeResolvers,
because Spring for GraphQL registers a default
ClassNameTypeResolver which implements the TypeResolver interface.
It tries to match the simple class name of the value to a
GraphQLObjectType. If it cannot find a match, it will continue
searching through super types, including base classes and
interfaces. This default TypeResolver is registered when
graphql.GraphQL is initialized.

The default ClassNameTypeResolver is sufficient if your Java model


maps 1:1 with your API, without any further configuration.
Optionally, you can modify the behaviour of the default
ClassNameTypeResolver (see documentation) or manually provide
your own TypeResolver via the RuntimeWiringConfigurer.

For this example schema, to make use of the default type resolver,
create a Pet Java interface, and Dog and Cat classes which implement
Pet.

package myservice. service;


interface Pet {
String name();
String ownerId();
}

package myservice. service;

record Dog( String name, String ownerId, Boolean doesBark)


implements Pet {
}

package myservice. service;

record Cat( String name, String ownerId, Boolean doesMeow)


implements Pet {
}

Then add two owner DataFetchers for Dog and Cat types.
package myservice. service;

import org. springframework. graphql. data. method. annotation


.QueryMapping;
import org. springframework. graphql. data. method. annotation
.SchemaMapping;
import org. springframework. stereotype. Controller;

@Controller
record PetsController( PetService petService) {

@QueryMapping
Pet favoritePet() {
return petService.getFavoritePet();
}

@SchemaMapping
Person owner( Dog dog) {
return petService.getPerson( dog.ownerId());
}

@SchemaMapping
Person owner( Cat cat) {
return petService.getPerson( cat.ownerId());
}

Here’s a sample query for favorite pet:


query bestPet {
favoritePet {
name
owner {
firstName
}
...on Dog {
doesBark
}
...on Cat {
doesMeow
}
}
}

If you have encountered a case where the default


ClassNameTypeResolver is not suitable, you can manually register a
TypeResolver by creating a RuntimeWiringConfigurer bean. The
RuntimeWiringConfigurer has RuntimeWiring.Builder as a parameter.

package myservice. service;

import graphql. schema. TypeResolver;


import graphql. schema. idl. TypeRuntimeWiring;
import org. springframework. context. annotation. Bean;
import org. springframework. context. annotation. Configuration;
import org. springframework. graphql. execution
.RuntimeWiringConfigurer;

import static graphql. schema. idl. TypeRuntimeWiring. newTypeWiring;

@Configuration
class Config {
TypeResolver petTypeResolver = ( env) -> {
// Your custom type resolver logic
};
@Bean
RuntimeWiringConfigurer runtimeWiringConfigurer() {
return wiringBuilder -> {
TypeRuntimeWiring petWiring = newTypeWiring( "Pet")
.typeResolver( petTypeResolver)
.build();

wiringBuilder.type( petWiring);
};
}

When we have a closer look at TypeResolvers in GraphQL Java later


in this chapter, we’ll see how to manually write our own
TypeResolver with pure GraphQL Java.

Arguments in Spring for GraphQL


To access a GraphQL argument, declare them via the @Argument
annotation.

For example, a search field in the schema takes arguments.


type Query {
search(pattern: String, limit: Int): String
}

With Spring for GraphQL, the arguments are declared with the
@Argument annotation.

package myservice. service;

import org. springframework. graphql. data. method. annotation


.Argument;
import org. springframework. graphql. data. method. annotation
.QueryMapping;
import org. springframework. stereotype. Controller;
@Controller
class SearchController {

@QueryMapping
String search( @Argument String pattern, @Argument int limit) {
// Your search logic here
}

The @Argument annotation automatically takes the method parameter


name, if available, as the name for the GraphQL argument. Note
this requires the -parameters compiler flag with Java 8+ or
debugging information from the compiler. You most likely have this
flag already enabled.

It’s also possible to customise the name through the annotation, as


in this example:
package myservice. service;

import org. springframework. graphql. data. method. annotation


.Argument;
import org. springframework. graphql. data. method. annotation
.QueryMapping;
import org. springframework. stereotype. Controller;

@Controller
class SearchController {

@QueryMapping
String search( @Argument( "pattern") String searchPattern,
@Argument( "limit") int maxElements) {
// Your search logic here
}

Spring for GraphQL makes it much easier to use input object


arguments. We’ll see later in this chapter that in GraphQL Java, we
always get java.util.Map for input objects. Spring for GraphQL
makes this step easier by binding GraphQL arguments to Java
classes automatically, if they are compatible.

For example, a search schema with an input object argument:


type Query {
search(input : SearchInput): String
}

input SearchInput {
pattern: String
limit: Int
}

The SearchInput record class:


package myservice. service;

record SearchInput( String pattern, int limit) {


}

And the search controller method:


package myservice. service;

import org. springframework. graphql. data. method. annotation


.Argument;
import org. springframework. graphql. data. method. annotation
.QueryMapping;
import org. springframework. stereotype. Controller;

@Controller
class SearchController {

@QueryMapping
String search( @Argument SearchInput input) {
// Your search logic here
}

}
The input object is bound to an instance of SearchInput. This is
easier to work with than the java.util.Map that represents input
objects when using GraphQL Java without Spring for GraphQL.

Without Spring for GraphQL’s argument injection, the input object


would be a map, which is not as convenient to work with.
package myservice. service;

import graphql. schema. DataFetchingEnvironment;


import org. springframework. graphql. data. method. annotation
.QueryMapping;
import org. springframework. stereotype. Controller;

import java. util. Map;

@Controller
class SearchController {

@QueryMapping
String search( DataFetchingEnvironment env) {
Map< String, Object> input = env.getArgument( "input");
String pattern = ( String) input.get( "pattern");
int limit = ( int) input.get( "limit");
// Your search logic here
}

More Spring for GraphQL inputs


Spring for GraphQL also supports the following method parameters
for schema mapping handler methods.

Argument Description
@Arguments Binding all arguments to a single
object
“Source” Access to the source (parent)
p
Argument instance ofDescription
the field
DataLoader A DataLoader from the
DataLoaderRegistry. See the chapter
about DataLoader
@ContextValue A value from the main
GraphQLContext in
DataFetchingEnvironment.See more
on context in the DataFetchers in
depth chapter
@LocalContextValue A value from the local
GraphQLContext in
DataFetchingEnvironment

GraphQLContext The entire GraphQLContext


java.security.Principal The currently authenticated
principal that made this request.
See the chapter about Security for
more. This is
SecurityContext.getAuthentication(

DataFetchingFieldSelectionSet The DataFetchingFieldSelectionSet


from DataFetchingEnvironment
Locale or Optional<Locale> The current locale for the request,
from DataFetchingEnvironment
DataFetchingEnvironment The entire DataFetchingEnvironment

Adding custom scalars in Spring for


GraphQL
In the Schema chapter, we discussed that in addition to the built-in
scalar types, it is possible to add custom scalars.
A common custom scalar to add is Date. In the Schema chapter we
saw that custom scalars must be declared in the schema:
"""
A custom scalar representing a date without any time zone.
Note how this custom scalar must be declared explicitly.
"""
scalar Date @specifiedBy(url:
"https://scalars.graphql.org/andimarek/local-date")

type Pet {
"String is a built-in scalar, therefore no declaration is requir
name: String
dateOfBirth: Date
}

You can use custom scalar implementations from libraries or


implement your own. In this example we’ll demonstrate the
GraphQL Java Extended Scalars library, which is maintained by the
GraphQL Java team.

To use the Date scalar in the GraphQL Java Extended Scalars library,
add the package.

For Gradle, add this to your build.gradle file:


implementation 'com.graphql-java:graphql-java-extended-scalars:19.1'

For Maven:
<dependency >
<groupId >com.graphql-java</groupId >
<artifactId >graphql-java-extended-scalars</artifactId >
<version >19.1</version >
</dependency >

Note: the major version number of the Extended Scalars library


corresponds to the linked major version of the main GraphQL Java
release. As examples in this book were written with Spring for
GraphQL 1.1.2 which uses GraphQL Java 19.2, we’ll use Extended
Scalars 19.1.

To wire custom scalars in Spring for GraphQL, create a


RuntimeWiringConfigurer bean. This will link the Date implementation
in graphql-java-extended-scalars to the scalar Date declared in your
schema.
package myservice. service;

import graphql. scalars. ExtendedScalars;


import org. springframework. context. annotation. Bean;
import org. springframework. context. annotation. Configuration;
import org. springframework. graphql. execution
.RuntimeWiringConfigurer;

@Configuration
class GraphQlConfig {

@Bean
RuntimeWiringConfigurer runtimeWiringConfigurer() {
return wiringBuilder ->
wiringBuilder.scalar( ExtendedScalars.Date);
}

The Spring Boot Starter automatically detects all


RuntimeWiringConfigurer beans.

Under the hood: DataFetchers inside


GraphQL Java
Spring for GraphQL automates much of the work to register
DataFetchers with schema fields. By using Spring for GraphQL
controller annotations, you might never even see the words
DataFetcher nor RuntimeWiring.
To build a deeper understanding of how Spring for GraphQL
connects data fetching logic to the schema, we are going to discuss
how GraphQL Java works under the hood. In your Spring for
GraphQL application, you won’t need to write any of the code in the
remainder of this chapter, but we hope that by showing you the
fundamentals, we concretely explain the “magic” of Spring.

In this second half of the chapter, we will build up to an executable


schema with pure GraphQL Java. A complete end-to-end example is
presented later in this chapter.

DataFetchers in GraphQL Java


Recall that a GraphQL operation is basically a tree of fields. We
need to instruct GraphQL Java how to load the data for every field.
Therefore, every field must have an associated DataFetcher.

Earlier in this chapter we covered how to create and register


DataFetchers with Spring for GraphQL’s controller annotations
such as @QueryMapping and @SchemaMapping. Now let’s see how to write
the equivalent code for the Pet schema without any Spring
automated “magic”.

A DataFetcher loads data for exactly one field. Inside GraphQL Java,
it is represented as a very generic Java interface.
public interface DataFetcher< T> {
T get( DataFetchingEnvironment environment) throws Exception;
}

The interface has only one method get with one argument
DataFetchingEnvironment. The returned result can be anything. This
interface directly reflects a core principle of GraphQL, it is agnostic
about where the data comes from.
Let’s implement DataFetchers for the simple Pet schema in the
earlier Spring for GraphQL example. Note that we are implementing
the initial example, where Pet is an object type and not an interface.
type Query {
favoritePet: Pet
}

type Pet {
name: String
owner: Person
}

type Person {
firstName: String
lastName: String
}

As a general rule, at least every root field needs to have a


DataFetcher implemented. Let’s implement the DataFetcher for
Query.favoritePet. Note that the logic is identical to the favoritePet
controller method in the Spring for GraphQL example earlier in this
chapter.
// Lower level layer service does the work of retrieving data
PetService petService = new PetService();

DataFetcher< Pet> favoritePetDataFetcher = ( env) -> {


return petService.getFavoritePet();
};

This DataFetcher also reflects best practice: a DataFetcher should


be slim. A DataFetcher should only take care of GraphQL-specific
aspects, and delegate the actual data retrieval to a layer below.

This particular favoritePetDataFetcher is quite simple, and does not


make use of the DataFetchingEnvironment (env) at all.

The Pet record class returned by the PetService is the same as the
class used in the Spring for GraphQL example earlier in this
chapter.
package myservice. service;

record Pet( String name, String ownerId) {


}

And this is the Person record class:


package myservice. service;

record Person( String id, String firstName, String lastName) {


}

As Pet contains only a ownerId and not a full Person object, we need to
load more data. Let’s implement another DataFetcher.
// Lower level layer service does the work of retrieving data
PetService petService = new PetService();

DataFetcher< Pet> favoritePetDataFetcher = ( env) -> {


return petService.getFavoritePet();
};

DataFetcher< Person> ownerDataFetcher= ( env) -> {


// Load the correct Person for the Pet
Pet pet = env.getSource();
return petService.getPerson( pet.ownerId());
};

In the ownerDataFetcher, we need to access the source object of the


DataFetchingEnvironment, which is an object of type Pet. The pet
instance information is necessary in order to retrieve the details of
the owner.

To highlight the convenience of Spring for GraphQL, let’s compare


to the owner DataFetcher we wrote previously:
@SchemaMapping
Person owner( Pet pet) {
return petService.getPerson( pet.ownerId());
}

Spring for GraphQL injects the Pet source object as a method


parameter, rather than having to manually access it from the
DataFetchingEnvironment with env.getSource().

Source objects in GraphQL Java


Let’s expand on the previous example to understand better how
DataFetchers work in practice and discuss the important source
object.

The source comes from the result of the parent field


DataFetcher, which was executed before the child DataFetcher.
The source can be anything, so the actual method signature in
DataFetchingEnvironment is very generic.

< T> T getSource(); // in DataFetchingEnvironment

In the previous section, we implemented an ownerDataFetcher. The


purpose of the ownerDataFetcher is to load a Person.
DataFetcher< Person> ownerDataFetcher= ( env) -> {
// Load the correct Person for the Pet
Pet pet = env.getSource();
return petService.getPerson( pet.ownerId());
};

We know that the parent DataFetcher favoritePet returned a Pet.


We can access the parent DataFetcher result via the source object of
the DataFetchingEnvironment.
Pet pet = env.getSource();

Then use the pet’s owner ID to look up the owner:


return petService.getPerson( pet.ownerId());
We can always safely assume that source is not null, except for the
root fields because a root field doesn’t have a parent field. If a
DataFetcher returns null, which can be a valid response, the
execution stops and the child DataFetchers are never invoked.

The source object is specific for every non-root DataFetcher. It gives


each child DataFetcher a source of data and additional information.

The source object also couples the DataFetchers together. The


ownerDataFetcher is not generic logic that can load any Person, rather
it requires that the parent field DataFetcher has loaded a Pet object.

RuntimeWiring in GraphQL Java


RuntimeWiring is the GraphQL Java class which contains
GraphQLCodeRegistry, which maps schema fields to DataFetchers.

We have implemented our two DataFetchers, but we haven’t yet


instructed GraphQL Java which schema fields these correspond to.
This is how to manually register the DataFetchers in the
RuntimeWiring with pure GraphQL Java code.

TypeRuntimeWiring queryWiring = newTypeWiring( "Query")


.dataFetcher( "favoritePet", favoritePetDataFetcher)
.build();
TypeRuntimeWiring petWiring = newTypeWiring( "Pet")
.dataFetcher( "owner", ownerDataFetcher)
.build();

RuntimeWiring runtimeWiring = newRuntimeWiring()


.type( queryWiring)
.type( petWiring)
.build();

Spring for GraphQL’s schema mapping annotations implement


DataFetchers and automatically register them to their correct field
and type coordinates in the schema in the GraphQLCodeRegistry inside
RuntimeWiring.

Creating an executable schema in


GraphQL Java
At the start of the annotated methods section of this chapter, we
mentioned there are a few key GraphQL Java classes which work
closely with DataFetchers. Let’s see how these classes come
together to create an executable schema in GraphQL Java.

A GraphQLSchema is both the structure of the API, and all the logic
needed to execute requests against it.

We saw earlier in this chapter that another key concept


RuntimeWiring, contains GraphQLCodeRegistry, which maps schema
fields to DataFetchers. Each DataFetcher needs to be registered in
the GraphQLCodeRegistry inside RuntimeWiring.

In pure GraphQL Java code, here is a creation of GraphQLSchema from


start to finish, and an example of a query being executed.
import graphql. GraphQL;
import graphql. schema. DataFetcher;
import graphql. schema. GraphQLSchema;
import graphql. schema. idl. RuntimeWiring;
import graphql. schema. idl. SchemaGenerator;
import graphql. schema. idl. SchemaParser;
import graphql. schema. idl. TypeDefinitionRegistry;
import graphql. schema. idl. TypeRuntimeWiring;
import myservice. service. Person;
import myservice. service. Pet;
import myservice. service. PetService;

import static graphql. schema. idl. RuntimeWiring. newRuntimeWiring;


import static graphql. schema. idl. TypeRuntimeWiring. newTypeWiring;

public class PureGraphQLJava {


p p Q {

public static void main( String[] args) {


String sdl = """
type Query {
favoritePet: Pet
}

type Pet {
name: String
owner: Person
}

type Person {
firstName: String
lastName: String
}
""";
TypeDefinitionRegistry parsedSdl = new SchemaParser().parse( s

// Lower level layer service does the work of retrieving data


PetService petService = new PetService();

DataFetcher< Pet> favoritePetDataFetcher = ( env) -> {


return petService.getFavoritePet();
};

DataFetcher< Person> ownerDataFetcher= ( env) -> {


// Load the correct Person for the Pet
Pet pet = env.getSource();
return petService.getPerson( pet.ownerId());
};

TypeRuntimeWiring queryWiring = newTypeWiring( "Query")


.dataFetcher( "favoritePet", favoritePetDataFetcher)
.build();
TypeRuntimeWiring petWiring = newTypeWiring( "Pet")
.dataFetcher( "owner", ownerDataFetcher)
.build();

RuntimeWiring runtimeWiring = newRuntimeWiring()


.type( queryWiring)
.type( petWiring)
.build();
GraphQLSchema schema = new SchemaGenerator()
.makeExecutableSchema( parsedSdl, runtimeWiring);
GraphQL graphQL = GraphQL.newGraphQL( schema).build();
System.out.println( "data:" + graphQL
.execute( "query pureGJ {favoritePet{name owner{firstName}}}")
.getData());
}

The schema is first parsed from a string. This schema could


alternatively be parsed from a file. Then, the TypeRuntimeWiring
objects containing our two DataFetchers are instantiated. Then the
TypeRuntimeWiring objects are registered in the RuntimeWiring. Finally,
the executable schema is created by combining both the parsed
schema and the RuntimeWiring. Luckily, you won’t have to write any
of this boilerplate code to instantiate an executable schema, as this
is automatically managed by Spring for GraphQL.

TypeResolver in GraphQL Java


If the type of the field is an interface or union, GraphQL Java needs
to determine the actual object of the value via a TypeResolver. In
GraphQL Java, a TypeResolver is a very generic Java interface:
public interface TypeResolver {
GraphQLObjectType getType( TypeResolutionEnvironment env);
}

Every interface and union type has an associated TypeResolver,


similar to how every field has an associated DataFetcher. The
TypeResolver interface needs to be implemented.

If using GraphQL Java without Spring for GraphQL’s default


ClassNameTypeResolver, we would have to implement our own
TypeResolver. It’s worth reviewing this example to understand how a
TypeResolver works without the Spring magic. For example, let’s
write a Pet TypeResolver for this expanded Pet schema.
type Query {
favoritePet: Pet
}

interface Pet {
name: String
owner: Person
}

type Dog implements Pet {


name: String
owner: Person
doesBark: Boolean
}

type Cat implements Pet {


name: String
owner: Person
doesMeow: Boolean
}

type Person {
firstName: String
lastName: String
}

If using GraphQL Java directly, this is how to implement a


TypeResolver for the Pet interface above. In this example, we map
Java classes to schema types. However, you could implement any
logic here to resolve types.
TypeResolver petTypeResolver = ( env) -> {
Object javaObject = env.getObject();
if ( javaObject instanceof Dog) {
return env.getSchema().getObjectType( "Dog");
} else if ( javaObject instanceof Cat) {
return env.getSchema().getObjectType( "Cat");
} else {
return null;
}
};

This TypeResolver is then registered with the RuntimeWiring via a


TypeRuntimeWiring, similar to how DataFetchers are registered. This
how to register the TypeResolver, as well as the new DataFetcher
wiring required for the Cat and Dog types.
// Lower level layer service does the work of retrieving data
PetService petService = new PetService();

DataFetcher< Pet> favoritePetDataFetcher = ( env) -> {


return petService.getFavoritePet();
};

DataFetcher< Person> ownerDataFetcher= ( env) -> {


// Load the correct Person for the Pet
Pet pet = env.getSource();
return petService.getPerson( pet.ownerId());
};

TypeResolver petTypeResolver = ( env) -> {


Object javaObject = env.getObject();
if ( javaObject instanceof Dog) {
return env.getSchema().getObjectType( "Dog");
} else if ( javaObject instanceof Cat) {
return env.getSchema().getObjectType( "Cat");
} else {
return null;
}
};

TypeRuntimeWiring queryWiring = newTypeWiring( "Query")


.dataFetcher( "favoritePet", favoritePetDataFetcher)
.build();

TypeRuntimeWiring petWiring = newTypeWiring( "Pet")


.typeResolver( petTypeResolver)
.build();

TypeRuntimeWiring catWiring = newTypeWiring( "Cat")


.dataFetcher( "owner", ownerDataFetcher)
.build();
TypeRuntimeWiring dogWiring = newTypeWiring( "Dog")
.dataFetcher( "owner", ownerDataFetcher)
.build();

RuntimeWiring runtimeWiring = newRuntimeWiring()


.type( queryWiring)
.type( petWiring)
.type( catWiring)
.type( dogWiring)
.build();

Most likely, you will not need to write nor register any TypeResolvers
and instead make use of Spring for GraphQL’s default
ClassNameTypeResolver. However, we hope this section gives you a
better understanding of how interfaces and unions are resolved in
GraphQL Java.

In this chapter, we covered the essentials of DataFetchers, the logic


that connects your schema to your data. We saw how to create
DataFetchers in Spring for GraphQL. We then took a look under the
hood in GraphQL Java. We saw how DataFetchers work in GraphQL
Java, and re-created the same DataFetchers and executable schema
without the Spring “magic”.

We hope you have a much better understanding of DataFetchers


and how Spring for GraphQL eliminates much of the boilerplate
code. We will cover more advanced topics in the DataFetchers in
depth chapter and take a deep dive into execution in the Execution
chapter.
Building a GraphQL service
This chapter focuses on the more hands-on aspects of building a
service with Spring for GraphQL, and makes use of the concepts we
covered in the previous chapters. We will build upon the service we
started in the Introduction chapter. By the end of this chapter, you
will implement a more substantial GraphQL service.

Spring for GraphQL


The Spring for GraphQL homepage is
https://spring.io/projects/spring-graphql and the source code can
be accessed on GitHub at https://github.com/spring-
projects/spring-graphql.

The best way to get a project up and running with Spring for
GraphQL is via the Spring Initializr tool at https://start.spring.io.
We previously created the application in the “Your first Spring for
GraphQL service” section of the Introduction chapter.

The Spring for GraphQL project contains multiple artifacts:

org.springframework.graphql:spring-graphql : Integrates
GraphQL Java with the Spring framework.

org.springframework.boot:spring-boot-starter-graphql : This is
the Spring Boot Starter for GraphQL.
org.springframework.graphql:spring-graphql-test: Testing
support for Spring for GraphQL. We’ll discuss this in the Testing
chapter.

In addition to the Spring for GraphQL repo, some Boot-specific code


is in the Spring Boot repository.

When we write “Spring for GraphQL”, we always mean Spring for


GraphQL with Spring Boot.

GraphQL Java
GraphQL Java is automatically included with Spring for GraphQL. If
you are using GraphQL Java directly, it can be added to your project
as a dependency via Maven or Gradle.

The GraphQL Java homepage is https://www.graphql-java.com and


the source code can be accessed on GitHub at
https://github.com/graphql-java/graphql-java.

GraphQL Java has minimal dependencies to maximize its usage.


SLF4J is the only key dependency.

Every version of GraphQL Java has two parts: the major and bug fix
part. At the time of writing, the latest Spring for GraphQL 1.1.2 uses
GraphQL Java 19.2. GraphQL Java doesn’t use semantic versioning.

Spring WebFlux or Spring MVC


Spring for GraphQL supports both reactive Spring WebFlux and
Spring MVC. When starting a new project, choose either
org.springframework.boot:spring-boot-starter-webflux

or
org.springframework.boot:spring-boot-starter-web

as a dependency. Spring for GraphQL automatically detects either


dependency. In this chapter, we will use WebFlux in the examples,
but all examples can be easily changed to their Spring MVC
equivalent by removing the Mono and Flux types.

Reading schemas
Spring for GraphQL then scans for schema files in
src/main/resources/graphql/ ending with *.graphqls or *.gqls. After
the schema files are found and successfully loaded, Spring for
GraphQL exposes the GraphQL API at the endpoint /graphql by
default.

Configuration properties
Spring for GraphQL offers configuration properties to adjust the
default behavior without writing any code.

Path: By default, the GraphQL API is exposed via /graphql. This can
be modified by setting spring.graphql.path to another value.

GraphiQL: GraphiQL is an interactive, in-browser GraphQL IDE.


Spring for GraphQL comes with built-in GraphiQL support, but it’s
not enabled by default. Activate it with
spring.graphql.graphiql.enabled=true. The default GraphiQL path is
/graphiql, which can be modified with spring.graphql.graphiql.path.

Schema files: Spring for GraphQL scans Schema Definition


Language (SDL) files with default file extensions *.graphqls and
*.gqls from the default location classpath:graphql/**/.
You can modify file extensions with
spring.graphql.schema.locations.file-extensions and modify the
location with spring.graphql.schema.locations.

Expanding our Spring for GraphQL


service
In the “Your first Spring for GraphQL service” section of the
introduction chapter, we built a minimal Spring for GraphQL
service and executed our first query. Let’s revisit key Spring for
GraphQL concepts from the past few chapters and build a more
fully featured GraphQL service.

Pet schema
Let’s continue with the GraphQL service we started in the “Your
first Spring for GraphQL service” section of the introduction
chapter, an API for pets. Review the Introduction chapter for
instructions on how to create and download a Spring for GraphQL
service.

We have a simple query field pets which returns basic information


about Pets. You should already have this schema file saved in the
file src/main/resources/graphql/schema.graphqls.
type Query {
pets: [Pet]
}

type Pet {
name: String
color: String
}
Controllers
Recall from the DataFetchers chapter that DataFetchers load data
for exactly one field. They are the most important concept in
executing a GraphQL request because they represent the logic that
connects your schema and your data.

We will use Spring for GraphQL’s annotation-based programming


model to register DataFetchers, which was previously discussed in
the DataFetchers chapter. @Controller components use annotations
to declare handler methods as DataFetchers for specific GraphQL
fields. As we have a query field pets, let’s use the shortcut
@QueryMapping annotation. We’ll represent Pets in Java as a record
class.
package myservice. service;

record Pet( String name, String color) {


}

package myservice. service;

import org. springframework. graphql. data. method. annotation


.QueryMapping;
import org. springframework. stereotype. Controller;

import java. util. List;

@Controller
class PetsController {

@QueryMapping
List< Pet> pets() {
return List.of(
new Pet( "Luna", "cappuccino"),
new Pet( "Skipper", "black"));
}

}
The @QueryMapping shortcut annotation registers the pets() method
as a DataFetcher for the query field pets. We did not have to specify
the schema field name, because it was automatically detected from
the method name. For more on @SchemaMapping and shortcut versions
such as @QueryMapping, see the annotated methods section in the
DataFetchers chapter.

In this example, we don’t need to manually write DataFetchers for


the Pet fields name and color. Recall that PropertyDataFetchers for
name and color are automatically registered, because the GraphQL
fields match the Java object’s properties. For more on
PropertyDataFetchers, see the earlier DataFetchers chapter.

The pets() method is currently returning an in-memory list. We’ll


change this to call another service in the next section.

Fetching data from an external service


GraphQL is agnostic about the source of your data. Let’s upgrade
our data source from an in-memory list to a more realistic scenario.
In a production service, it’s likely you want to fetch data from
another service, such as a REST service with the help of Spring
WebClient.

package myservice. service;

import org. springframework. graphql. data. method. annotation


.QueryMapping;
import org. springframework. stereotype. Controller;
import org. springframework. web. reactive. function. client
.WebClient;
import reactor. core. publisher. Flux;

@Controller
class PetsController {

WebClient petWebClient;
PetsController( WebClient.Builder builder) {
this.petWebClient = builder.baseUrl( "http://pet-service").bui
}

@QueryMapping
Flux< Pet> pets() {
return petWebClient.get()
.uri( "/pets")
.retrieve()
.bodyToFlux( Pet.class);
}

In our upgraded pets DataFetcher, the bodyToFlux call returns a


Flux<Pet> based on the JSON response of http://pets-service/pets.
This is a placeholder URL in place of a REST endpoint. If you do not
yet have an external service in mind, you can revert to using in-
memory objects for the following examples.

Source object
A core concept of GraphQL is that you can write flexible queries to
retrieve exactly the information you need.

Let’s expand our Pet schema to model one owner per pet.
type Query {
pets: [Pet]
}

type Pet {
name: String
color: String
owner: Person
}
type Person {
name: String
}

Now we can query the owner’s name for each pet.


query petsAndOwners {
pets {
name
owner {
name
}
}
}

Our external Pet service only contains a reference to an owner with


ownerId. To retrieve information such as the owner’s name, we have to
contact a separate owner service. To model this, add the field ownerId
to the Pet class.
package myservice. service;

record Pet( String name, String color, String ownerId) {


}

Add a class for Person, the type of the owner field:


package myservice. service;

record Person( String name) {


}

To fetch the owner information for a given list of pets, we need to


implement a new DataFetcher called owner that takes a Pet as an
argument. The argument Pet is called the “source object” since it
determines the pet we retrieve owner data for.
package myservice. service;

import org. springframework. graphql. data. method. annotation


.QueryMapping;
import org. springframework. graphql. data. method. annotation
p g p g g p q
.SchemaMapping;
import org. springframework. stereotype. Controller;
import org. springframework. web. reactive. function. client
.WebClient;
import reactor. core. publisher. Flux;
import reactor. core. publisher. Mono;

@Controller
class PetsController {

WebClient petWebClient;
WebClient ownerWebClient;

PetsController( WebClient.Builder builder) {


this.petWebClient = builder.baseUrl( "http://pet-service").bui
this.ownerWebClient = builder.baseUrl( "http://owner-service")
.build();
}

@QueryMapping
Flux< Pet> pets() {
return petWebClient.get()
.uri( "/pets")
.retrieve()
.bodyToFlux( Pet.class);
}

// New
@SchemaMapping
Mono< Person> owner( Pet pet) {
return ownerWebClient.get()
.uri( "/owner/{id}", pet.ownerId())
.retrieve()
.bodyToMono( Person.class);
}

The owner DataFetcher returns the owner for exactly one pet. We use
bodyToMono to convert the JSON response to a Java object.
To account for the new Pet schema field owner, we added a new
property ownerId to the Pet class. This ownerId is used to construct
the URL to fetch owner information. Note that the Pet class
contains an ownerId which is not exposed, so a client cannot query it.

Every time we fetch data for a non-root field (such as owner), we


use a source object as an argument to identify the parent for
returned data from DataFetcher. See the DataFetcher chapter for
more on the source object in Spring for GraphQL and how it is
represented in GraphQL Java.

GraphQL arguments
Fields can have arguments, which have their type defined in the
schema. Arguments can be either optional or required. As discussed
in the Schema chapter, an argument is required if it is non-null
(indicated with !) and there is no default value (declared with =).

Let’s introduce a new query field pet which takes an argument id.
The argument is of type ID. ID is a built-in scalar type representing a
unique identifier. It is marked as non-nullable by adding !.
type Query {
pets: [Pet]
pet(id: ID!): Pet # New field
}

type Pet {
id: ID! # New field
name: String
color: String
owner: Person
}

type Person {
name: String
}
This is how to query the name of a specific pet with the id argument
“123”.
query myFavoritePet {
pet(id: "123") {
name
}
}

We can conveniently access this GraphQL argument by using Spring


for GraphQL’s @Argument annotation. See more on arguments in
Spring for GraphQL in the DataFetchers chapter.

Let’s add the id field to the Pet class.


package myservice. service;

record Pet( String id, String name, String color, String ownerId) {
}

package myservice. service;

import org. springframework. graphql. data. method. annotation


.Argument;
import org. springframework. graphql. data. method. annotation
.QueryMapping;
import org. springframework. graphql. data. method. annotation
.SchemaMapping;
import org. springframework. stereotype. Controller;
import org. springframework. web. reactive. function. client
.WebClient;
import reactor. core. publisher. Flux;
import reactor. core. publisher. Mono;

@Controller

class PetsController {

WebClient petWebClient;
WebClient ownerWebClient;

PetsController( WebClient.Builder builder) {


this.petWebClient = builder.baseUrl( "http://pet-service").bui
this.ownerWebClient = builder.baseUrl( "http://owner-service")
.build();
}

@QueryMapping
Flux< Pet> pets() {
return petWebClient.get()
.uri( "/pets")
.retrieve()
.bodyToFlux( Pet.class);
}

@SchemaMapping
Mono< Person> owner( Pet pet) {
return ownerWebClient.get()
.uri( "/owner/{id}", pet.ownerId())
.retrieve()
.bodyToMono( Person.class);
}

// New
@QueryMapping
Mono< Pet> pet( @Argument String id) {
return petWebClient.get()
.uri( "/pets/{id}", id)
.retrieve()
.bodyToMono( Pet.class);
}

As we saw in the Query Language chapter, an argument can also be


an input object.

An input object type describes a group of input fields where each


has an input type. In SDL, an input object is declared with the input
keyword.

Let’s introduce a new query field petSearch which takes an input


object PetSearchInput.
type Query {
pets: [Pet]
pet(id: ID!): Pet
petSearch(input : PetSearchInput!): [Pet] # New field
}

# New input type


input PetSearchInput {
namePattern: String
ownerPattern: String
}

type Pet {
id: ID!
name: String
color: String
owner: Person
}

type Person {
name: String
}

Let’s add a Java class PetSearchInput to represent the input type in


the schema. To access this input object as an argument, we add it as
a parameter to the petSearch method.
package myservice. service;

record PetSearchInput( String namePattern, String ownerPattern) {


}

package myservice. service;

import org. springframework. graphql. data. method. annotation


.Argument;
import org. springframework. graphql. data. method. annotation
.QueryMapping;
import org. springframework. graphql. data. method. annotation
.SchemaMapping;
import org. springframework. stereotype. Controller;
import org. springframework. web. reactive. function. client
.WebClient;
import reactor core publisher Flux;
import reactor. core. publisher. Flux;
import reactor. core. publisher. Mono;

@Controller
class PetsController {
WebClient petWebClient;
WebClient ownerWebClient;

PetsController( WebClient.Builder builder) {


this.petWebClient = builder.baseUrl( "http://pet-service").bui
this.ownerWebClient = builder.baseUrl( "http://owner-service")
.build();
}

@QueryMapping
Flux< Pet> pets() {
return petWebClient.get()
.uri( "/pets")
.retrieve()
.bodyToFlux( Pet.class);
}

@SchemaMapping
Mono< Person> owner( Pet pet) {
return ownerWebClient.get()

.uri( "/owner/{id}", pet.ownerId())


.retrieve()
.bodyToMono( Person.class);
}

@QueryMapping
Mono< Pet> pet( @Argument String id) {
return petWebClient.get()
.uri( "/pets/{id}", id)
.retrieve()
.bodyToMono( Pet.class);
}

// New
@QueryMapping
Flux< Pet> petSearch( @Argument PetSearchInput input) {
// perform the search
}
}

Mutations
As we saw in the Query Language chapter, data is changed in
GraphQL with mutation operations. Let’s add a mutation to change
a pet’s name.
type Query {
pets: [Pet]
pet(id: ID!): Pet
petSearch(input : PetSearchInput!): [Pet]
}

# New mutation field


type Mutation {
changePetName(id: ID!, newName: String!): ChangePetNamePayload
}

# New type
type ChangePetNamePayload {
pet: Pet
}

input PetSearchInput {
namePattern: String
ownerPattern: String
}

type Pet {
id: ID!
name: String
color: String
owner: Person
}

type Person {
name: String
}
The return type for the mutation field ends with Payload to follow a
quasi-standard naming convention for mutation response types.

This is a mutation request to change the name of the pet with the id
“123” to “Mixie”.
mutation changeName {
changePetName(id: "123", newName: "Mixie") {
pet {
name
}
}
}

Let’s add the mutation payload class.


package myservice. service;

record ChangePetNamePayload( Pet pet) {


}

Implement the mutation DataFetcher with the shortcut


@MutationMapping annotation.

package myservice. service;

import org. springframework. graphql. data. method. annotation


.Argument;
import org. springframework. graphql. data. method. annotation
.MutationMapping;
import org. springframework. graphql. data. method. annotation
.QueryMapping;
import org. springframework. graphql. data. method. annotation
.SchemaMapping;
import org. springframework. http. MediaType;
import org. springframework. stereotype. Controller;
import org. springframework. web. reactive. function
.BodyInserters;
import org. springframework. web. reactive. function. client
.WebClient;
import reactor. core. publisher. Flux;
import reactor. core. publisher. Mono;
import java. util. Map;

@Controller
class PetsController {

WebClient petWebClient;
WebClient ownerWebClient;

PetsController( WebClient.Builder builder) {


this.petWebClient = builder.baseUrl( "http://pet-service").bui
this.ownerWebClient = builder.baseUrl( "http://owner-service")
.build();
}

@QueryMapping
Flux< Pet> pets() {
return petWebClient.get()
.uri( "/pets")
.retrieve()
.bodyToFlux( Pet.class);
}

@SchemaMapping
Mono< Person> owner( Pet pet) {
return ownerWebClient.get()
.uri( "/owner/{id}", pet.ownerId())
.retrieve()
.bodyToMono( Person.class);
}

@QueryMapping
Mono< Pet> pet( @Argument String id) {
return petWebClient.get()
.uri( "/pets/{id}", id)
.retrieve()
.bodyToMono( Pet.class);
}

@QueryMapping
Flux< Pet> petSearch( @Argument PetSearchInput input) {
// perform the search
}
// New
@MutationMapping
Mono< ChangePetNamePayload> changePetName(
@Argument String id,
@Argument String newName
) {
Map< String, String> changeNameBody = Map.of(
"name", newName
);
return petWebClient.put()
.uri( "/pets/{id}", id)
.contentType( MediaType.APPLICATION_JSON)
.body( BodyInserters.fromValue( changeNameBody))
.retrieve()
.bodyToMono( ChangePetNamePayload.class);
}

In our changePetName method, we update the name of a pet with an


HTTP PUT request. The response from the HTTP PUT contains the
newly modified pet’s details.

A mutation controller method can have any of the method


arguments available to schema mapping handler methods. See the
DataFetchers chapter for a list of method arguments. In this case,
the changePetName method takes the two parameters id and newName,
representing arguments of the mutation field.

Unions, interfaces, and TypeResolver


To demonstrate unions and interfaces, we will use a simpler
example to demonstrate how Spring for GraphQL’s default
TypeResolver works. If you would like to use this Pet interface in
combination with the earlier examples of this chapter, we’ll leave
the task of deserializing data up to you, as deserialization depends
on the data source you choose.
There are many kinds of pets in the world, each with slightly
different attributes. It would be better to represent Pet as an
interface in our schema. Let’s add two Pet implementations, Dog
and Cat. You can add your favourite Pet implementation too.

To demonstrate unions, we’ll also add a Creature union, and a new


query field for creatures.
type Query {
creatures: [Creature] # New
}

# Changed from type to interface


interface Pet {
id: ID!
name: String
color: String
}

# New implementation of Pet


type Dog implements Pet {
id: ID!
name: String
color: String
barks: Boolean
}

# New implementation of Pet


type Cat implements Pet {
id: ID!
name: String
color: String
meows: Boolean
}

# New
type Human {
name: String
}

# New
union Creature = Dog | Cat | Human
Note that the GraphQL spec requires that unions only contain
object types. We must specify the object types Dog and Cat, we
cannot specify the interface Pet.

Let’s mirror these changes in our Java model. Let’s represent Pet as
an interface.
package myservice. service;

interface Pet {
String id();
String name();
String color();
}

And create two new classes, Dog and Cat, which implement the Pet
interface.
package myservice. service;

record Dog( String id,


String name,
String color,
boolean barks) implements Pet {
}

package myservice. service;

record Cat( String id,


String name,
String color,
boolean meows) implements Pet {
}

Let’s create a new Human class.


package myservice. service;

record Human( String name) {


}
Let’s register a DataFetcher for the new creatures query field. Note
that the incoming data must be converted into the correct Java class
representation. This will depend on the implementation of the
remote service. For example, the service may return a field for each
Creature to indicate its type.

In the example below we have included an in-memory list, so you


can verify this union works before setting up how to request from
the remote service and how to deserialize data.
package myservice. service;

import org. springframework. graphql. data. method. annotation


.QueryMapping;
import org. springframework. stereotype. Controller;
import reactor. core. publisher. Flux;

@Controller
class PetsController {

@QueryMapping
Flux< Object> creatures() {
// Add your fetching logic

// In-memory example
return Flux.just(
new Dog( "Dog01", "Spot", "Yellow", true),
new Cat( "Cat01", "Chicken", "Orange", true),
new Human( "Donna"));

Try out the query below with the GraphiQL interactive playground
at http://localhost:8080/graphiql. To enable GraphiQL, please add
the following to your application.properties file.
spring.graphql.graphiql.enabled=true
query allTheThings {
creatures {
...on Dog {
name
barks
}
... on Cat {
name
meows
}
... on Human {
name
}
}
}

If a field type is an interface or union, GraphQL Java needs to


determine the actual object type of the value via a TypeResolver.

In this service, we will make use of Spring for GraphQL’s default


ClassNameTypeResolver, which tries to match the simple class name of
the value to a GraphQLObjectType. If it cannot find a match, it will
continue searching through super types, including base classes and
interfaces. The default ClassNameTypeResolver is sufficient because in
our service, the Java model maps 1:1 with the API. If your model
doesn’t exactly match your API, see the TypeResolver section in the
DataFetchers chapter for how to register a custom TypeResolver.

The default ClassNameTypeResolver is registered when the Spring Boot


starter initializes graphql.GraphQL, and therefore no further
configuration is required for our service. We do not need to
manually write a TypeResolver for either the Pet interface nor the
Creature union.

In this chapter, you built a more substantial Spring for GraphQL


application making use of concepts we have discussed in previous
chapters.
If you haven’t already, enable the GraphiQL interactive playground
by adding this to your application.properties file.
spring.graphql.graphiql.enabled=true

Start your service and navigate to the GraphiQL interactive


playground at http://localhost:8080/graphiql. Try out various
queries and mutations and verify your code is working end to end.

In the chapters that follow, we will discuss more specialized topics


such as schema design and GraphQL errors. Later in the book, we
will cover more advanced topics including a deep dive into execution
inside the GraphQL Java engine.
Subscriptions
There are three types of operations supported by GraphQL: queries,
mutations, and subscriptions. In this chapter we’ll expand on the
subscription operation. While queries and mutations are similar in
many respects, subscriptions are quite different.

A subscription is a long-lived request, where clients want to be


informed about certain events on the server side. A subscription can
be active for minutes or hours, whereas a query or mutation is
active for a few milliseconds to a few seconds at most. With a
subscription, the clients will get updates from the server for every
relevant change, meaning that there is a “stream” of data, rather
than a single response.

Let’s walk through an example of an online store. The client wants


to be notified each time someone creates a new order, when it
happened, and by whom. The subscription operation would look like
this:
subscription myOrders {
newOrderCreated {
id
createdTime
customer {
name
}
}
}

First, the client requests a newOrderCreated subscription and then


receives the first response 10 seconds later.
{
"newOrderCreated": {
"id": "123",
"createdTime": "2015-07-06T04:11:11.000Z",
"customer": {
"name": "Andi"
}
}
}

Then another response after 2 more seconds. And so on.


{
"newOrderCreated": {
"id": "124",
"createdTime": "2015-07-06T04:11:13.000Z",
"customer": {
"name": "Elli"
}
}
}

Note how there are multiple responses for the single subscription
request. This differs from queries and mutations, where one request
corresponds to exactly one response.

Protocol-wise, subscriptions also need a different solution, because


HTTP was not designed for data to be sent from the server to the
client via a long-lived connection. Most commonly, WebSockets are
used as the protocol for subscriptions. Spring for GraphQL supports
the WebSocket protocol out of the box.

Getting started
Let’s implement subscriptions with Spring for GraphQL.

Start with a new Spring for GraphQL project with Spring Initializr at
https://start.spring.io/, as we did in the “Your first Spring for
GraphQL service” section of the Introduction chapter. Choose
Spring for GraphQL as a dependency, and choose either Spring Web
or Spring Reactive Web as a dependency. In the following examples
we will use Spring Reactive Web, which includes Spring WebFlux. If
you prefer to use subscriptions with WebMVC instead of WebFlux,
add org.springframework.boot:spring-boot-starter-websocket as a
dependency, and adjust the examples by removing Mono and Flux.

Let’s add a subscription field to a new schema.


# Every schema needs a Query type
type Query {
notUsed: String
}

type Subscription {
hello: String
}

In the controller, add an annotated method hello. This method is


annotated with @SubscriptionMapping, which registers this
DataFetcher to the subscription field hello in the schema.
package myservice. service;

import org. springframework. graphql. data. method. annotation


.SubscriptionMapping;
import org. springframework. stereotype. Controller;
import reactor. core. publisher. Flux;

import java. time. Duration;


import java. util. List;

@Controller
class HelloController {

@SubscriptionMapping
Flux< String> hello() {
Flux< Integer> interval = Flux.fromIterable( List.of( 0, 1, 2))
.delayElements( Duration.ofSeconds( 1));
return interval.map( integer -> "Hello " + integer);
}
}

As the results of subscriptions are streams of data, Flux from


Reactor is a perfect abstraction. In our example, we emit data three
times: “Hello 1”, “Hello 2” and “Hello 3”, each with a one-second
delay.

In order to enable subscriptions, we have to add a new Spring


configuration property, setting the path where we want to expose
our subscription.
spring.graphql.websocket.path=/graphql

Let’s also enable the GraphiQL playground for manual testing.


spring.graphql.graphiql.enabled=true

Let’s test our subscription. Start the service and open the GraphiQL
playground at http://localhost:8080/graphiql.

In the playground, execute a new subscription. Click the play button


to execute, and you should see a response as in the screenshot
“Subscription request”.
subscription myFirstSubscription {
hello
}
Subscription request

Then we will see the response changing every second, from “Hello
0” to “Hello 1” to “Hello 2”, as shown in the screenshot “Hello 2
answer”.

Hello 2 answer

We have a working subscription. Congratulations!

If you instead saw this “isTrusted” error message, this is a generic


message.
{
"errors": [
{
"isTrusted": true
}
]
}

Please double-check that you have enabled the WebSocket path in


your configuration file.
spring.graphql.websocket.path=/graphql

Execution
Similarly to queries and mutations, we implement subscriptions as
DataFetchers. In order to deliver a stream of responses, GraphQL
Java requires that it return a org.reactivestreams.Publisher instance.
The Reactive Streams initiative defines this interface to provide a
standard for asynchronous stream processing.

When using GraphQL Java with Spring, we use Flux from Project
Reactor, which implements Publisher. We used Flux in the example
earlier in this chapter.

Spring for GraphQL takes care of bridging the transport layer


WebSocket protocol to this Publisher. Every time it emits a new
result, we send it to the client.

The GraphQL specification requires a subscription request always


has exactly one root field, unlike queries and mutations. For
example, the subscription below is invalid because it has multiple
roots.
# Not valid
subscription tooManyRoots {
newCats {
name
}
newDogs {
name
}
}

Additionally, we can only subscribe to one subscription per request.

We execute all sub-selections below the root field, as we would for a


query or mutation. Let’s return to our orders example from earlier
in the chapter.
subscription myOrders {
newOrderCreated {
id
createdTime
customer {
name
}
}
}

The DataFetcher for newOrderCreated returns a Publisher. Every new


event emitted from it starts a normal query execution for the sub-
selection with the event payload as source object.

Let’s walk through subscription execution step by step:

We receive a new subscription request from a client, which is


executed by calling the DataFetcher for newOrderCreated, that returns
a Publisher.

When that Publisher emits a new event, we execute the sub-


selection { id createdTime customer } with the event payload as a
source object. All of this happens exactly the same way as described
in the fetching data section of the Execution chapter: we invoke all
three DataFetchers for id, createdTime, and customer and complete
the returned values. If customer returns a non-null value, we invoke
the DataFetcher for name.

We send the result back to the client. When the Publisher emits a
new event, the execution starts again, and we send a new result to
the client.

Once the Publisher signals that it has finished, the whole request
finishes.

It’s important to note that the data emitted by the Publisher is not
the actual data sent to the client, but only used as input for the sub-
selection, which follows the same GraphQL execution rules as
queries and mutations.

Protocol
Subscriptions require a way for the server to inform the client about
new events. The protocol that comes closest to a standard for
subscriptions is a WebSocket-based protocol: graphql-ws. Spring for
GraphQL supports this protocol out of the box.

This protocol enables any kind of GraphQL requests to be executed


with it, but in practice it is used primarily for subscription requests
because the WebSocket protocol is more complicated than HTTP.

To activate the WebSocket path, set the path via the


spring.graphql.websocket.path configuration property.

spring.graphql.websocket.path=/graphql-subscriptions

The GraphiQL playground separates HTTP and WebSocket URLs, so


we can set two different endpoints when we open GraphiQL.
http://localhost:8080/graphiql?path=/graphql&wsPath=/graphql-
subscriptions

Spring for GraphQL handles the URLs for us automatically. When


we open http://localhost:8080/graphiql, we get redirected
automatically to a URL with the correct parameters depending on
our configuration.

It is also possible to offer both the WebSocket protocol and normal


HTTP protocol via the same URL, as we did in our example earlier
in the chapter.

Client support
We can use the graphql-ws protocol with a variety of different
clients. However, as some clients might not support graphql-ws by
default, additional setup might be required. The graphql-ws GitHub
repo contains a list of recipes for different clients.
In this chapter we discussed GraphQL subscriptions. Later in the
Testing chapter, we’ll discuss how to test subscriptions.
Request and response
In this chapter, we will take a closer look at requests and responses
for GraphQL, including the HTTP protocol.

Transport protocols and serialization


It might surprise you that the GraphQL spec does not specify any
transport protocol for how a client communicates with a server.
Although it was a deliberate decision to exclude transport concerns
from the spec, in practice HTTP has become the most commonly
used protocol. The community agrees on the HTTP spec outlined in
Serving over HTTP best practices on http://graphql.org, which
serves as a quasi-standard. There is a proposal for a GraphQL over
HTTP specification, but it is not yet official at the time of writing.

Transport protocols are handled at the Spring for GraphQL level.


GraphQL Java does not dictate any transport protocol.

JSON is the most common serialization choice. Although the


GraphQL spec does not dictate any particular serialization format,
given the sheer popularity of JSON, the spec includes a section on
JSON serialization.

Request
The most important elements of a GraphQL request are the query,
operation name, and variables. Every GraphQL request over HTTP
is a POST encoded as application/json, with the body being a JSON
object:
{
"query": "<document>",
"variables": {
<variables>
},
"operationName": "<operationName>"
}

The goal is to execute exactly one GraphQL operation. The HTTP


endpoint is always the same, often ending with /graphql by
convention.

In the request body, the first key “query” is actually a GraphQL


document, rather than a GraphQL query. A GraphQL document can
contain one or many operations, and is represented in the request
as a JSON string. This key is not optional.

The next key “variables” is a JSON map with all the variables for the
operation. This key is optional, as operation variables are optional.

The third key “operationName” specifies which operation to execute


in the document. This is optional and only required when there are
multiple operations in the document.

Under the hood, the transport protocol is handled by Spring for


GraphQL. An HTTP request in Spring for GraphQL is represented by
a WebGraphQlRequest containing HTTP-specific information such as
headers.

And at the GraphQL Java level, a GraphQL request is represented by


a graphql.ExecutionInput instance. Here are the most important
fields:
public class ExecutionInput {
private final String query;
private final String operationName;
private final RawVariables rawVariables;
// and more fields
}

query represents the “query” key in the JSON object, and


operationName represents the “operationName” key. rawVariables
represents the variables map in the JSON object. The word raw
reflects that the variables have not yet been coerced. We’ll discuss
variable coercion in the Execution chapter.

Note that transport concerns are managed at the Spring for


GraphQL level. GraphQL Java’s ExecutionInput does not specify any
transport protocol. The most important fields in this class
correspond to the JSON object in the request body we saw
previously.

Response
Over HTTP, the response to a GraphQL request is a JSON object:
{
"data": <data>,
"errors": <list of GraphQL errors>,
"extensions": <map of extensions>
}

If the data key is not present, the errors key must be present to
explain why no data was returned. If the data key is present, the
errors key can be present too, in the case where partial results are
returned. Note that null is a valid value for data.

The extensions key is optional. The value is a map and there are no
restrictions on its contents.
Under the hood in GraphQL Java, the response is represented as
graphql.ExecutionResult, which mirrors the JSON response.

public interface ExecutionResult {


< T> T getData();
List< GraphQLError> getErrors();
Map< Object, Object> getExtensions();
// and more methods
}

We will discuss GraphQL errors in greater detail in the next chapter.

HTTP status codes


The response’s HTTP status code depends on whether the GraphQL
Java engine was invoked.

If the request is rejected before the GraphQL Java engine is


invoked, we use HTTP status codes to indicate the problem. For
example, a 401 Unauthorized code is returned for authentication
failures, or 400 Bad Request if the request itself is not a GraphQL
request (e.g. the body is missing a “query” key).

If the request is rejected after the GraphQL Java engine is invoked,


the 200 OK status code is always returned. Any errors are represented
as GraphQL errors in the JSON response body.

Why would we return a 200 OK code even when there are errors in
the response? The reason for this model is to enable more flexible
requests and partial responses compared to a REST API. For
example, a partial response is still valuable, so it is returned with a
200 OK status code, and errors in the response to explain why part of
the data could not be retrieved.

The challenge with this model is that analyzing the response now
requires two steps:
1. Check the HTTP status code
2. If the status code is 200 OK, check for any GraphQL errors in the
response

This is a gotcha for developers with REST API experience. To


determine whether a request succeeded, remember to check both
the HTTP status code and the errors key of a response.

HTTP headers
We strongly recommend that information needed to understand the
request should be part of the GraphQL operation, and not passed in
via request headers.

This general rule comes from the intention to express the API
completely in the GraphQL schema. Let’s demonstrate this via a
counterexample, where an HTTP header “Customer-Id” is sent
alongside a query for the field ordersByCustomerId. In the schema, the
field is defined as:
type Query {
ordersByCustomerId: [Order]
}

Imagine you are reading the schema for the first time, wanting to
understand the API. There is no information to indicate that a
“Customer-Id” header is essential for the ordersByCustomerId field.
The schema becomes an incomplete description of the API.

We must have the customer ID to make sense of the request. It is


much better to explicitly require the customer ID as an argument in
the query request.
type Query {
ordersByCustomerId(id: ID!): [Order]
}
A person reading this improved schema would easily understand
that there must be an id argument provided to fulfil the request. As
the id argument is non-nullable, GraphQL validation and tooling
will also require the argument be provided to proceed with the
request.

We recommend sending information needed to understand the


request in the GraphQL operation. However, that doesn’t mean
request headers can’t be used at all. You should continue to use
request headers to send meta or auxiliary data, which is not
necessary to understand the GraphQL request.

One prominent example is sending authentication information.


Although authentication is required to execute the request, we can
understand the intent of the request without seeing credentials.

Another example is sending beta flags via request headers. If the


client wants to use certain fields that are not yet stable, we could
require some special header such as “beta-features”. With or
without this beta flag information, the request on its own make
sense.

A third example is tracing information such as request IDs.


Metadata like request IDs are useful for logging but are not
essential to understand the GraphQL request.

Intercepting requests
Spring for GraphQL provides WebGraphQlInterceptor to intercept
requests.

Spring for GraphQL automatically handles the HTTP protocol,


including the invocation of GraphQL Java which actually executes
the request. The default URL is /graphql, which we can customize
via the config property spring.graphql.path.
WebGraphQlRequestand WebGraphQlResponse represent the GraphQL
request and response over either HTTP or WebSocket. It contains
GraphQL-specific information as well as HTTP or WebSocket
details.

We can access and change the request with WebGraphQlInterceptor.


For example, we can intercept a request and add a “special-header”
to the response headers.
package myservice. service;

import org. springframework. graphql. server. WebGraphQlInterceptor;


import org. springframework. graphql. server. WebGraphQlRequest;
import org. springframework. graphql. server. WebGraphQlResponse;
import org. springframework. stereotype. Component;
import reactor. core. publisher. Mono;

@Component
class MyInterceptor implements WebGraphQlInterceptor {

@Override
public Mono< WebGraphQlResponse> intercept(
WebGraphQlRequest request, Chain chain
) {
return chain.next( request)
.map( response -> {
response.getResponseHeaders().add( "special-header", "true
return response;
});
}
}

Another use case is to change execution based on a request header.


In this example, clients are required to send a special beta flag
“beta-features” to request beta fields which are not yet stable. In the
interceptor, if this “beta-features” header is present, we add “beta-
features” to the GraphQLContext so this information can be accessed
later in the execution. We’ll cover GraphQLContext in more detail
in the DataFetchers in depth chapter.
package myservice. service;

import org. springframework. graphql. server. WebGraphQlInterceptor;


import org. springframework. graphql. server. WebGraphQlRequest;
import org. springframework. graphql. server. WebGraphQlResponse;
import org. springframework. stereotype. Component;
import reactor. core. publisher. Mono;

@Component
class BetaFeaturesInterceptor implements WebGraphQlInterceptor {

@Override
public Mono< WebGraphQlResponse> intercept(
WebGraphQlRequest request, Chain chain
) {
boolean betaFeatures = request
.getHeaders()
.containsKey( "beta-features");

request.configureExecutionInput(( executionInput, builder) ->


executionInput
.getGraphQLContext()
.put( "beta-features", betaFeatures);
return executionInput;
});

return chain.next( request);


}

The example above changed the GraphQL Java request object


ExecutionInput. We can also access and change the entire GraphQL
Java response ExecutionResult. In the intercept method, return your
customized WebGraphQLResponse.

For example, if you already attach a request ID to incoming


requests, it is useful to add the request ID to the extensions section
of the GraphQL response. This is very helpful for debugging issues,
as the person using your API can give you a request ID to quickly
find relevant logs.
This is how to append an extra key to the extensions of the
response:
package myservice. service;

import graphql. ExecutionResult;


import org. springframework. graphql. server. WebGraphQlInterceptor;
import org. springframework. graphql. server. WebGraphQlRequest;
import org. springframework. graphql. server. WebGraphQlResponse;
import org. springframework. stereotype. Component;
import reactor. core. publisher. Mono;

import java. util. HashMap;


import java. util. Map;

@Component
class ChangeResponse implements WebGraphQlInterceptor {

@Override
public Mono< WebGraphQlResponse> intercept(
WebGraphQlRequest request,
Chain chain
) {
return chain.next( request)
.map( response -> {
// response is a WebGraphQLResponse containing
// the ExecutionResult

ExecutionResult executionResult = response.getExecutionRes


Map< Object, Object> newExtensions = new HashMap<>();
if ( executionResult.getExtensions() != null) {
newExtensions.putAll( executionResult.getExtensions());
}
// Replace value with your request ID mechanism
newExtensions.put( "request_id", "YOUR_REQUEST_ID_HERE");

return response.transform( builder ->


builder.extensions( newExtensions).build()
);
});
}

}
In this chapter we discussed requests and responses for GraphQL in
greater detail, and demonstrated how to access these objects in
Spring for GraphQL.

In the next chapter, we’re going to build on these concepts and


discuss GraphQL errors.
GraphQL errors
What happens when things go wrong? How do we communicate
errors from our GraphQL service?

In the previous chapter we started discussing GraphQL responses,


which are JSON objects with three key entries: data, errors, and
extensions.
{
"data": <data>,
"errors": <list of GraphQL errors>,
"extensions": <map of extensions>
}

In this chapter we’ll discuss GraphQL errors in detail. We will


discuss how errors are presented to the client and how to customise
GraphQL errors in our Spring for GraphQL service.

There are broadly two kinds of GraphQL errors: request errors and
field errors. We’ll walk through how GraphQL errors are presented
with examples.

Request errors
A request error is raised during a request. The GraphQL response
will contain an errors key, but no data key. For example, a request
error will be raised if a request contains a GraphQL syntax error,
such as a missing closing curly brace }.
Request errors are raised before execution begins. In other words,
request errors are raised before any DataFetchers are invoked. A
request error is usually the fault of the requesting client.

Some examples of request errors from the GraphQL spec are:

parsing errors, including GraphQL syntax errors


validation errors in the GraphQL document, which typically
indicates that the request is not compliant with the GraphQL
spec
unable to determine which operation to execute
invalid input values for variables

For example, this request is invalid GraphQL syntax:


query invalid {
{
foo
}
}

The response contains an error, including a message indicating what


went wrong:
{
"errors": [
{
"message": "Invalid Syntax : offending token '{'
at line 2 column 5",
"locations": [
{
"line": 2,
"column": 5
}
],
"extensions": {
"classification": "InvalidSyntax"
}
}
]
}
Every error must contain the key message, with a description of the
error. In this case, the message indicates the request contained
invalid GraphQL syntax. If the error can be linked to a location in
the GraphQL document, it should be presented to make the error
easier to find. The location information indicates the invalid syntax
is at line 2 and column 5 of the GraphQL document.

The GraphQL spec also allows for an optional key extensions, which
is a map of additional data. There are no restrictions on the contents
of this map. It’s useful for error logging to categorise errors, so
GraphQL Java provides a number of common error classifications.
On top of this, Spring for GraphQL adds a few extra error
classifications. You can also create custom error classifications.
We’ll explain classifications in more detail later in this chapter. In
this example, the InvalidSyntax classification was added by GraphQL
Java.

Note how there was no data key in the GraphQL response, because
no DataFetchers were invoked. Execution was terminated when the
syntax error was detected.

Let’s see another example of a request error, when we attempt to


request a field doesNotExist that does not exist in the Query type of
the schema.
query missing {
doesNotExist
}

{
"errors": [
{
"message": "Validation error (FieldUndefined@[doesNotExist]) :
Field 'doesNotExist' in type 'Query' is undefined",
"locations": [
{
"line": 2,
"column": 5
}
],
],

"extensions": {
"classification": "ValidationError"
}
}
]
}

The message communicates that the field doesNotExist does not exist
in the Query type. As this error can be linked to a location in the
GraphQL document, it is provided.

In the extensions map, GraphQL Java inserts the classification


ValidationError. It is invalid to ask for a field in a GraphQL request
which does not exist in the schema.

Note that there was no data key in the GraphQL response, because
no DataFetchers were invoked. Execution was terminated when the
validation error was detected.

Field errors
Field errors are raised during the execution of a field, resulting in a
partial response. In other words, an error raised during the
execution of a DataFetcher.

For example, a basic Pet schema with friends.


type Query {
favoritePet: Pet
}

type Pet {
id: ID
name: String
friends: [Pet]
}
We make a request with this query:
query whoIsAGoodPup {
favoritePet {
name
friends {
name
}
}
}

Let’s write the friends DataFetcher to intentionally fail, to simulate


an error.
package myservice. service;

import java. util. List;

record Pet( String id, String name, List< String> friendIds) {


static List< Pet> pets = List.of(
new Pet( "1", "Luna", List.of( "2")),
new Pet( "2", "Skipper", List.of( "1"))
);
}

@Controller
class PetsController {
@QueryMapping
Pet favoritePet() {
// Logic to return the user's favorite pet.
// Logic mocked with Luna the Dog.
return Pet.pets.get( 0);
}

@SchemaMapping
List< Pet> friends( Pet pet) {
throw new RuntimeException( "Something went wrong!");
}
}

The DataFetcher that loads the friends of the pet throws an


exception. This is the GraphQL response for the whole query, in
JSON.
{
"errors": [
{
"message": "INTERNAL_ERROR for f8f26fdc-4",
"locations": [
{
"line": 4,
"column": 9
}
],
"path": [
"favoritePet",
"friends"
],
"extensions": {
"classification": "INTERNAL_ERROR"
}
}
],
"data": {
"favoritePet": {
"name": "Luna",
"friends": null
}
}
}

Our example demonstrates that field errors don’t cause the whole
request to fail, meaning a GraphQL result can contain “partial
results”, where part of the response contains data, while other parts
are null. We were able to load Luna’s name, but none of her friends.
Because we were unable to load friends, the “friends” key has the
value null.

Partial results have consequences for the client. Clients must always
inspect the “errors” of the response in order to determine whether
an error occurred or not. Note that you cannot rely on a null value
to indicate a GraphQL error was raised, instead the errors key of the
response must always be inspected. A DataFetcher can return both
data and errors for a given field.
We have one error with a “message” key, representing the exception
thrown inside the friends DataFetcher. The “locations” key
references the position of friends in the query and “path” of the field
that caused the error.

In the extensions key, Spring for GraphQL inserts the classification


INTERNAL_ERROR. We’ll expand on error classifications added by Spring
for GraphQL later in this chapter.

How errors appear in the response


To recap, a GraphQL response contains data, errors, and extensions.
The response is returned as a JSON object. In GraphQL Java, this
response is represented as an ExecutionResult, containing data, a
java.util.List of graphql.GraphQLError objects, and a java.util.Map of
extensions.
{
"data": <data>,
"errors": <list of GraphQL errors>,
"extensions": <map of extensions>
}

The GraphQL spec defines a few rules for when data and errors are
present in the response.

The errors entry will be present if there are errors raised during
the request. If there are no errors raised during the request,
then the errors entry must not be present. If the errors entry is
present, it is a non-empty list.

If the data entry of the response is not present, the errors entry
must be present. For example, a request error will have no data
entry in the response, so the errors entry must be present.
If the data entry of the response is present (including the value
null), the errors entry must be present if and only if one or more
field errors were raised during execution.

The extensions entry is optional and there are no restrictions on the


contents of this map.

In GraphQL Java, an individual error is represented by the


graphql.GraphQLError interface, which contains these methods.

String getMessage();
List< SourceLocation> getLocations();
List< Object> getPath();
Map< String, Object> getExtensions();
ErrorClassification getErrorType();

The GraphQL spec defines an error can contain up to four keys:


message, locations, path, and extensions. While the first four methods
directly represent keys in the JSON response, ErrorClassification is
a GraphQL Java-specific interface that allows us to classify an error.
This is what appears in the classification field inside the extensions
map of the GraphQL response.

Error classifications
Classifying errors is useful for logging and monitoring. GraphQL
Java enables error classifications to be added to responses. Note
that although classifying errors is not required by the GraphQL
spec, we have found it invaluable for categorizing errors in metrics.

GraphQL Java includes common error classifications, and Spring for


GraphQL adds a few additional classifications. You can also create
custom classifications.

GraphQL Java provided error classifications


GraphQL Java provides commonly used error classifications in
graphql.ErrorType. These classifications implement the
graphql.ErrorClassification interface.

Classification Description
InvalidSyntax Request error due to invalid
GraphQL syntax
ValidationError Request error due to invalid
request
OperationNotSupported Request error if request attempts
to perform an operation not
defined in the schema
DataFetchingException Field error raised during data
fetching
NullValueInNonNullableField Field error when a field defined
as non-null in the schema
returns a null value

Spring for GraphQL provided error classifications

Additionally, Spring for GraphQL adds a few more useful error


classifications, which also implement the
graphql.ErrorClassification interface.

BAD_REQUEST
UNAUTHORIZED
FORBIDDEN
NOT_FOUND
INTERNAL_ERROR

If an exception is unresolved, it will be categorized by default as an


INTERNAL_ERROR with a generic message including the category name
and executionId. You can customize this with a
DataFetcherExceptionResolverAdapter which will be discussed later in
this chapter.

GraphQL Java provided error classifications are also used in Spring


for GraphQL. For example, a request with invalid GraphQL syntax
will contain a response error with the classification InvalidSyntax.
Likewise, ValidationError, OperationNotSupported, and
NullValueInNonNullableField classifications are also used by Spring
for GraphQL.

Note that an HTTP response code of 200 (OK) is always returned if


the GraphQL engine is invoked, even if there are errors in the
response. The error classification is included in the errors key of the
GraphQL response. For example, if a database is unavailable, this
will cause a field error to be raised, and a GraphQL error will appear
in the response. The HTTP response code for a request with this
database issue will still be 200. This may seem surprising if you
have experience with other APIs such as REST. See the discussion
on HTTP status codes in the previous chapter for a detailed
explanation.

Custom error classifications

You can also create custom error classifications by implementing


the ErrorClassification interface from GraphQL Java. Error
classifications are added to the GraphQL error builder, as we’ll see
later in this chapter.

For example, you could categorize different types of failed


authorization checks, rather than the catch-all “UNAUTHORIZED”
error classification. You can create separate error classifications for
each category of authorization failure, and use these categories to
monitor authorization failures with metrics on the server side.

How to return errors


We’ve seen how errors are presented in GraphQL responses. Now
let’s discuss how to return errors inside our Spring for GraphQL
service. We’ll discuss two fundamental ways to return field errors:
by throwing exceptions during a DataFetcher invocation or by
returning errors via DataFetcherResult.

Throw exception during DataFetcher


invocation
One way to return errors is to raise an exception during DataFetcher
invocation. When a DataFetcher throws an exception, GraphQL
Java converts it into a GraphQL error, which is added to the overall
GraphQL response and the value of field is set to null. In our earlier
example where the friends DataFetcher raised a RuntimeException,
the friends field in the data was set to null. In a more realistic
service, the friends database may be unavailable, causing an
exception to be raised.

When an exception is thrown during DataFetcher invocation,


GraphQL Java’s DataFetcherExceptionHandler is called. A GraphQL
Java application can register a DataFetcherExceptionHandler, and
Spring for GraphQL provides a built-in handler
ExceptionResolversExceptionHandler. This handler allows for a chain
of exception resolvers to be registered. This is configured for use by
the Spring Boot starter. As we saw in the error examples earlier in
this chapter, no additional code is required to configure exception
handling.

However, if you would like to customize exception resolution, you


can register DataFetcherExceptionResolvers. Spring for GraphQL
makes it easy to register custom exception resolvers, which we’ll
demonstrate in the next section on customizing exception
resolution.
Even if you have not explicitly selected Spring WebFlux as a
dependency for your GraphQL service, internally Spring for
GraphQL uses Reactor types such as Mono. Spring for GraphQL uses
reactor-core as a dependency. Note that while Reactor is used
internally by Spring for GraphQL, you do not need to use Reactor
types in your code for your service. If the words Mono and Flux are
unfamiliar, it is fine to skip over the discussion of Reactor types in
the Spring for GraphQL internals.

Each registered exception resolver will be called, one after another,


until one returns a list of GraphQL errors (which can be empty). If
the returned Mono from the exception resolver completes empty,
without emitting a list, the exception remains unresolved, and we
invoke the next exception resolver.

If no exception resolver takes care of the exception, Spring for


GraphQL creates a default error with an INTERNAL_ERROR classification
with a generic error message. Spring for GraphQL makes an
intentionally opaque message to avoid leaking implementation
details. In general, we suggest you only show clients what they need
to know to understand the response. Avoid revealing
implementation details. For example, avoid dumping stack traces in
the error message. You should separately monitor detailed
exception information such as stack traces.

Customizing exception resolution


If you would like to customize exception resolution, Spring for
GraphQL offers a DataFetcherExceptionResolverAdapter abstract class
that already implements much of the contract for you.

For example, let’s implement an exception resolver that overrides


the actual exception message.
package myservice. service;

i h l h
import graphql. GraphQLError;
import graphql. GraphqlErrorBuilder;
import graphql. schema. DataFetchingEnvironment;
import org. springframework. graphql. execution
.DataFetcherExceptionResolverAdapter;
import org. springframework. graphql. execution. ErrorType;
import org. springframework. stereotype. Component;

@Component
class CustomErrorMessageExceptionResolver
extends DataFetcherExceptionResolverAdapter {
@Override
protected GraphQLError resolveToSingleError( Throwable ex,
DataFetchingEnvironment env) {
return GraphqlErrorBuilder.newError( env)
.errorType( ErrorType.INTERNAL_ERROR) // Error classificat
.message( "My custom message") // Overrides the message
.build();
}
}

The Spring Boot starter automatically detects


DataFetcherExceptionResolver beans as part of instantiating
GraphQlSource, the same step that also automatically loads schema
files and more. The abstract class
DataFetcherExceptionResolverAdapter implements
DataFetcherExceptionResolver. In this example,
CustomErrorMessageExceptionResolver extends
DataFetcherExceptionResolverAdapter and is annotated with @Component
to indicate it should be scanned by the Spring Boot starter.

When extending DataFetcherExceptionResolverAdapter, you have the


choice of either overriding the methods resolveToSingleError or
resolveToMultipleErrors. As the names suggest, a
resolveToSingleError will resolve an exception to a single GraphQL
error, and resolveToMultipleErrors will resolve an exception to a list
of GraphQL errors.
Note that the DataFetcherExceptionResolverAdapter does not require
you to use Reactor types, as parts of the contract requiring Reactor
types have already been implemented for you.

The recommended way to create new instances of


graphql.GraphQLError is via graphql.GraphqlErrorBuilder. Please note
the slightly different capitalization in names. This builder is more
convenient than implementing the graphql.GraphQLError interface
directly. Use the factory method that takes a
DataFetchingEnvironment, as shown in this example.

If you are using GraphQL Java without Spring for GraphQL, note
that the handler in GraphQL Java is different. GraphQL Java uses
the SimpleDataFetcherExceptionHandler implementation. This handler
creates a ExceptionWhileDataFetching error with the classification
ErrorType.DataFetchingException.

Return data and errors with


DataFetcherResult
The only way to return both data and errors for a field is with
DataFetcherResult. Contrast this to throwing exceptions, which will
always set the field value to null.

For example, we have a list of some pets, but not all of it was
available during execution. Let’s take a look at a simple Pet schema.
type Query {
myPets: [Pet]
}

type Pet {
id: ID
name: String
}
package myservice. service;

import java. util. List;

record Pet( String id, String name) {


static List< Pet> pets = List.of(
new Pet( "1", "Luna"),
new Pet( "2", "Skipper")
);
}

package myservice. service;

import graphql. ErrorType;


import graphql. GraphQLError;
import graphql. GraphqlErrorBuilder;
import graphql. execution. DataFetcherResult;
import graphql. schema. DataFetchingEnvironment;
import org. springframework. graphql. data. method. annotation
.QueryMapping;
import org. springframework. stereotype. Controller;

import java. util. List;

@Controller
class PetsController {

@QueryMapping
DataFetcherResult< List< Pet>> myPets(
DataFetchingEnvironment env) {
// Your partial list of data here
// In-memory Pet example
List< Pet> result = List.of( Pet.pets.get( 1));

GraphQLError error = GraphqlErrorBuilder.newError( env)


.errorType( ErrorType.DataFetchingException)
.message( "Data could only be partially loaded")
.build();
return DataFetcherResult.< List< Pet>>newResult()
.data( result)
.error( error)
.build();
}
}

In this chapter, we discussed GraphQL errors in depth. We


discussed request and field errors, and how they are presented in
the GraphQL response. Then we demonstrated how to raise errors
in our Spring for GraphQL service, and how to customize exception
handling.
Schema design
Schema design is a critical part of implementing a GraphQL service.
In this chapter, we will discuss key principles and best practices
from our experiences running GraphQL services.

Schema-first and implementation-


agnostic
We should design our schema deliberately as a standalone activity.
From experience, we’ve seen it is not the best approach to infer a
schema or generate it from another source. Generated schemas will
always be of lower quality.

GraphQL differs from a database schema or REST API that we


might use to fetch data for a query.

Implementation details should not influence schema design and


should never be leaked. We always need to ask: is this information
really needed to be exposed and is it the right format?

For example, just because the current database backing the API
doesn’t allow the User.name field to be null doesn’t automatically
mean it should also be non-nullable in the schema. The design
needs to be justified independent of the current implementation.

If we build a GraphQL API and the implementation involves calling


a user service via REST, it doesn’t mean the user resource for a
REST API is what we want to expose. We should design a User type
in GraphQL based on the requirements and then look into how we
can implement it.

Of course, we might need to adjust our ideal solution based on


implementation constraints. We need to be pragmatic and deliver a
working API. The point remains that we should always start with
the schema first in order to design the best API.

Evolution over versioning


We prefer evolution over versioning as the recommended approach
for GraphQL. In REST APIs, we might see versions frequently, often
as part of the URL like /v2/. In a GraphQL API, there is only one
URL and one schema that grows over time with incremental
changes.

For example, we might start out with just a User.


type Query {
user(id: ID!): User
}

type User {
id: ID!
name: String
}

Then we might change the schema to include address information


for a user like this.
type Query {
user(id: ID!): User
}

type User {
id: ID!
name: String
address: Address
}

type Address {
street: String
city: String
country: String
}

This schema change makes our API is richer, clients can choose
whether to use the new functionality by including an address in
their user query selection fields or not.

It’s more challenging to evolve the schema when a change is a


breaking change. For example, if we decide that the current name:
String is not enough, we introduce a better UserName object with
fields like legalName and preferredName.
type Query {
user(id: ID!): User
}

type User {
id: ID!
name: UserName
}

type UserName {
legalName: String
preferredName: String
}

However, this schema change breaks all existing clients, who are
using name, such as this query.
query broken {
user(id: "123") {
name
}
}
The query would suddenly become invalid and always result in an
error because name is no longer a User string field, and now it’s a
UserName object that needs a sub-selection.

This is how to manage breaking changes:

1. Introduce alternative (if applicable)


2. Deprecate old field
3. Monitor usage and/or wait a certain amount of time
4. Remove old field

Let’s walk through how we would manage a breaking change in our


User example.

First, we add a new field for userName, while leaving the existing
User field there for now.
type Query {
user(id: ID!): User
}

type User {
id: ID!
name: String
userName: UserName
}

type UserName {
legalName: String
preferredName: String
}

Then we deprecate the old field with the built-in @deprecated


directive. Note that directives appear after the declaration that they
decorate such as name.
type Query {
user(id: ID!): User
}
type User {
id: ID!
name: String
@deprecated(reason: "Use richer alternative `userName`.")
userName: UserName
}

type UserName {
legalName: String
preferredName: String
}

The next step is very context specific. Depending on the kind of


client API and any service guarantees, we might monitor its usage
and wait until nobody uses the field anymore. Or we might simply
give all clients a certain amount of time to migrate, such as 6
months, or do a combination of both.

Then in the last step, remove the field.


type Query {
user(id: ID!): User
}

type User {
id: ID!
userName: UserName
}

type UserName {
legalName: String
preferredName: String
}

Now we have finished the gradual transition from name: String to


userName: UserName.

Of course, you should adjust your approach based on your situation.

For example, you may choose to immediately make a breaking


change if:
you detect that nobody is using that particular field
you are certain that clients can handle the breaking change
the change is for an internal company testing API, which is not
used in production

You might also choose to retain a deprecated field for the


foreseeable future rather than removing the field.

Our other general recommendation for these kinds of breaking


changes is to design the schema for evolution and avoid them as
much as possible. This chapter covers specific recommendations for
schema evolution.

Every production GraphQL API is bound to face breaking changes.


The general goal is to minimize the amount of breaking changes,
not to avoid them completely.

Connected
A GraphQL API should resemble a connected or graph-like structure
for maximum client flexibility. The client should be able to
“traverse” from one piece of data to another related one, in a single
request.

For example, this schema is not connected.


type Query {
issue: Issue
userById(id: ID!): User
}

type Issue {
description: String
ownerId: ID
}

type User {
id: ID
name: String
}

This schema requires two queries to retrieve the owner’s name for
an issue.
# First
query myIssue {
issue {
ownerId
}
}
# returns "123"

# Second
query myUser {
userById(id: "123") {
name
}
}

This is the better, more connected schema.


type Query {
issue: Issue
}

type Issue {
description: String
owner: User
}

type User {
id: ID
name: String
}

Now the client can directly query the full User object for the issue in
one query.
query connected {
issue {
owner {
name
}
}
}

It is good to look for any <name>Id: String/ID or other unique


identifier fields in a schema, then verify if there is a good reason to
retain them, or whether you might directly connect them to some
other data.

Of course, there are limitations to how connected something can be,


depending on the data sources you have access to.

Schema elements are cheap


Don’t make types and fields overly generic in an attempt to make
them reusable.

For example, you might consider reusing input objects like this
search filter.
type Query {
searchPets(filter: SearchFilter): [Pet]
searchHumans(filter: SearchFilter): [Human]
}

input SearchFilter {
name: String
ageMin: Int
ageMax: Int
}

Reusing the input object is not a good idea because it couples the
two fields unnecessarily together. What happens if we would like to
add a breed field for pets? Now we have either a filter for humans
that includes a breed, or we need to deprecate fields and introduce
new ones.
The same principle is true for output types. This example can seem
tempting especially for mutations.
type Mutation {
deleteUser(input : DeleteUserInput!): ChangeUserPayload
updateUser(input : UpdateUserInput!): ChangeUserPayload
}

type ChangeUserPayload {
user: User
}

This example has the same problem as the reused input objects.
Once we want to change the return type for just one mutation, we
have a problem.

The other trap we might fall into is trying to combine multiple use
cases into one field. Fields are cheap, like any other element. Our
service doesn’t get slower, or have any other direct negative effects
with a larger amount of fields. We should make single-purpose
fields explicit with specific naming.

Compare these two examples:


type Query {
pet(id: ID, name: String): Pet
}

vs
type Query {
petById(id: ID!): Pet
petByName(name: String!): Pet
}

The second version is better in every aspect. We have better names


and the arguments are marked as non nullable. We can also again
evolve the schema much more easily.
Nullable fields
One of the most misunderstood topics in schema design is the
nullability of fields. When starting to learn GraphQL, it confuses
many people that fields are nullable by default, which leads to
beginners making almost all fields non-nullable.

In GraphQL, almost all fields should be nullable to allow results to


return more data. Let’s take a closer look at how GraphQL handles
null data through examples.

Consider the case where a field is marked as non-nullable, but the


data is null during execution. The schema gives the assurance that
the field is never null, so GraphQL cannot return null. Instead, the
parent is set to null if possible. If the parent of the original field is
also non-nullable, then we set the parent of the parent to null if
possible, and so on. The error is propagated through the hierarchy
of parent fields until a field can be set to null.

Let’s step through null result handling with a concrete schema


example:
type Query {
a: A
}

type A {
b: B
}

type B {
c: C!
}

type C {
d: String!
}

If field d is null during execution for this query,


query myQuery {
a {
b {
c {
d
}
}
}
}

then we end up with this result.


{
"data": {
"a" : {
"b": null
}
}
}

d was null, but marked as non-nullable. Therefore, we tried to set


the parent c to null. But c is also non-nullable, therefore we try and
successfully set b to null.

If we change the schema so that field b: B! can’t be null either, then


we end up with this.
{
"data": {
"a" : null
}
}

And finally, if we change field a to a: A!, then we end up with


everything set to null.
{
"data": null
}

If the error propagates all the way up, we set everything to null and
we even lose the result of other root fields. Let’s walk through a
more realistic example.

A pet and human schema:


type Query {
pet: Pet!
human: Human
}

type Pet {
name: String
}

type Human {
name: String
}

This looks innocent, but if we query pet and human at the same time,
query petAndHuman {
pet {
name
}
human {
name
}
}

and if pet field fails to load, but human field load succeeds, we still
end up with no data.
{
"data": null
}

This is because we marked pet as non-nullable and the error


propagates up and wipes out all results.

Now that we understand how GraphQL handles non-null, we can


rephrase what it means that we declare a field non-null: “A non-
nullable field is so essential that all other fields make little sense
without it”.

The most basic examples are id fields. Normally, if the fetching of


the id is unsuccessful, we can’t guarantee anything else and
therefore we should make it non-nullable.
type User {
id: ID!
name: UserName
address: Address
}

In this example, name and address are not as fundamental and


therefore not declared non-null.

Sometimes there are other fields that we should also make non-
nullable. A User could have a primary email to login in, but it is
reasonable to assume that this is such an important field that we
don’t want to serve any data if we can’t load the primary email.
type User {
id: ID!
primaryEmail: String! # also non-null
name: UserName
address: Address
}

An interesting consequence of thinking about non-nullability in this


way is that root fields should always be nullable.

One common mistake with nullability is arguing based on the


current implementation. For example:
type Query {
orders: [Order]
}

type Order {
id: ID!
customer: Customer!
# And more order fields here
}

type Customer {
id: ID!
}

Perhaps this schema looks fine because we store all the current
orders in one database and if we can load an order, then we also
load the customer at the same time. But now imagine that we
change our architecture in the future and decide to introduce an
order service and a separate customer service. Suddenly we have a
situation where we could load the order, but not the customer,
resulting in the whole Order being null when the error propagates
up.

If we look at customer again from the angle of “which fields are


essential”, then we realize that non-nullable isn’t a good choice
either. Other order data (which we have not shown in the example)
might be still valuable, even when we can’t load the customer.

One special case where non-nullable fields often make sense is


inside lists. For example:
type Query {
orders: [Order!]
}

The root field itself is nullable, but the elements inside the list are
not nullable. Even if we take current or future implementations into
consideration where we could load some orders, but not all, we
mostly don’t want to burden the client with special error handling.

To summarize the recommendations about non-nullable Fields:

Root fields are always nullable


Essential fields like id or key are non-nullable
Elements inside lists probably are non-nullable
All other fields probably should be nullable

Nullable input fields and arguments


Input fields are nullable by default, but in practice, we usually want
to make as many of them non-nullable as possible. Non-nullable
input fields and arguments have the simple advantage of clearly
communicating that we need this input to the API user, while
forcing us to be specific about the use case.

A nullable input field or argument often signals that we might have


a field that is too generic, and we should think about how we can
make them non-nullable.
type Query {
user(id: ID, name: String): User
}

Both arguments are nullable and the field itself is too generic. It is
better to change fields that must be present to be non-nullable.

Consider the counterexample where a name field inside the input


type has a special behavior for null, to indicate the user ought to be
deleted.
input UpdateUserInput {
id: ID!
name: String # null indicating deletion of the user
}

This is not a good solution because it’s harder for a user to


understand. It is much better to split into two inputs.
input ChangeUserInput {
id: ID!
name: String!
}
input DeleteUserInput {
id: ID!
}

As with fields, the elements inside a list are often an excellent good
candidate for making non-nullable.

Pagination for lists with Relay’s cursor


connection specification
We strongly recommend that all lists use pagination, unless the list
size is small and limited based on the domain.

A simple list such as pets: [Pet] can quickly become too large for
clients to handle. A simple list restricts clients to only two options:
requesting all the data, or none at all. If requested, the list will be
returned in its entirety, regardless of the size.

A list of hundreds of elements can cause a noticeable slowdown in


page loading time, due to the sheer time required to send the
response back to the client. As the list continues to grow in size, it
will become infeasible for a service to send all list elements before
the connection timeout, or before the user’s patience runs out.

We strongly recommend that all lists use pagination unless their


size is very small and it is very clear that the list can’t grow further
based on the domain. For example, the number of planets in the
Solar System is limited, so planets would not need pagination.
Based on our experience we recommend considering pagination for
lists larger than 25-50 elements.

Relay’s cursor connections specification


The most common pagination approach in GraphQL comes from
Relay’s cursor connections specification, and has become the de
facto standard for how GraphQL schemas should handle large lists.
Relay is a JavaScript framework for fetching GraphQL in React
applications.

The Relay connections specification is a “cursor based pagination”,


meaning requests are slices of the overall list relative to a “cursor”.
This cursor identifies the position within the overall list where we
start a slice from. Then we pick another cursor and slice again. We
can slice backwards or forwards. Notably, we can’t skip any
elements, we must request a fresh slice relative to a cursor.

The Relay connections specification implements cursor based


pagination with a few concepts: Connections, Edges, Nodes, and
PageInfo. We’ll explain these concepts alongside an example Pet
schema.

While there is quite some ceremony around pagination, the effort is


worthwhile in order to produce a good API.

Schema
This is an example Pet schema implementing the Relay connections
specification. We’ll go into further details of the specification after
walking through example queries.
type Query {
pets(first: Int, after: String, last: Int, before: String):
PetConnection
}

type PetConnection {
edges: [PetEdge]
pageInfo: PageInfo!
}
type PetEdge {
cursor: String!
node: Pet!
}

type PageInfo {
startCursor: String
endCursor: String
hasNextPage: Boolean!
hasPreviousPage: Boolean!
}

type Pet {
name: String
# Your additional Pet fields here
}

Query and response


This is how to query the first 2 pets with a schema implementing
the Relay connections specification.
query myPets {
pets(first: 2) {
edges {
cursor
node {
name
}
}
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
}
}

Let’s start at the top of the query. The first argument limits the
result to a maximum of 2 elements. We don’t supply any cursor
argument, because we don’t yet have a cursor.

In the next layer of the query are our connection fields, edges and
pageInfo.

An edge is a wrapper around the actual entity we want to iterate


over, in this example a node representing a Pet. The name in this
query is the name field of a Pet. Edges also provide metadata such as
cursor.

At the same level of edges, we also query pageInfo for general


information about the results, so can we move forward or backward
in the list.

An example response to the query could look like this.


{
"pets": {
"edges": [
{
"cursor": "ABCD123",
"node": {
"name": "Luna"
},
{
"cursor": "XYZ789",
"node": {
"name": "Skipper"
}
],
"pageInfo": {
"startCursor": "ABCD123",
"endCursor": "XYZ789",
"hasNextPage": true,
"hasPreviousPage": false
}
}
}

Requesting more pages


In our initial query, we received data for two pets with their
corresponding cursors. In the response, hasNextPage was true, so we
can request the next slice of data.

To request the next 10 pets after “Skipper” (which had a cursor of


“XYZ789”), we would query:
query morePets {
pets(first: 10, after: "XYZ789") {
edges {
cursor
node {
name
}
}
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
}
}

We could also go backwards, and request the one pet before


“Skipper” (which had a cursor of “XYZ789”):
query previousPet {
pets(last: 1, before: "XYZ789") {
edges {
cursor
node {
name
}
}
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
}
}
Key concepts of Relay’s cursor
connections specification
We previously walked through how to use pagination as a client.
Now let’s take a closer look at how to model pagination in our
schema.

Connection
A connection represents a page of data (edges), with additional
metadata to enable further page requests (pageInfo).

Instead of returning a list, pagination means we return a connection


object with the name <Entity>Connection, such as PetConnection in
our example.
type Query {
pets(
first: Int,
after: String,
last: Int,
before: String
): PetConnection
}

As we saw in the previous query examples, first and last are of type
Int because they represent how many objects we want to request.
after and before are of type String because they are cursors that
identify a position within a list of elements.

Note that you can choose to support only first/after or last/before,


if you don’t want to support pagination in both directions.

You could also add more arguments to filter elements, such as a


namePattern to filter pets by name:

type Query {
pets(
first: Int,
after: String,
last: Int,
before: String,
namePattern: String
): PetConnection
}

The <Entity>Connection type must have at least the two fields edges
and pageInfo.
type PetConnection {
edges: [PetEdge]
pageInfo: PageInfo!
}

An edge is a wrapper containing the element data and additional


metadata. An edge type is named <Entity>Edge. In our example, we
are interested in pages of Pets so call this type a PetEdge.

Pagination metadata is always called PageInfo and shared across all


edges.

We can add more fields to a connection type. For example, a


connection type could contain a totalCount field. Although, note that
adding totalCount can be problematic because it might not be easy to
support when the underlying architecture changes.

Edges
The edges of a connection represent a page of data. An edge is a
wrapper object that contains a data element (e.g. a Pet) and
metadata. It is named in the format <Entity>Edge. It must have at
least two fields: the cursor for the current element and the actual
element named node. Optionally, additional fields can be added.
type PetEdge {
cursor: String!
node: Pet!
}
PageInfo

The PageInfo type contains pagination metadata summarizing all the


requested edges in a query. It must contain the following fields:
type PageInfo {
startCursor: String
endCursor: String
hasNextPage: Boolean!
hasPreviousPage: Boolean!
}

PageInfosummarizes the current page’s location and whether


additional data can be requested before or after the current page.

Optional shortcut: adding nodes to a connection


type
You might have noticed in previous examples, we had a cursor field
for every element inside the node field, as well as a summary of
cursor information in the PageInfo type. If you are only interested in
cursor information for the overall page, and not for each individual
element, you can use a shortcut to shorten your query.

You can add a direct nodes field on a connection type.


type PetConnection {
edges: [PetEdge]
nodes: [Pet] # Optional shortcut
pageInfo: PageInfo!
}

This allows us to directly query pet data with nodes rather than via
edges. We retrieve the startCursor and endCursor for the page, rather
than the cursor for every pet as we did in the initial pagination
response example.
query shortcut {
pets(first: 2) {
nodes {
name
}
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
}
}

Compare the query above to the initial pagination example in this


section, which is longer because it queries pet data with nodes via
the edges field.
query myPets {
pets(first: 2) {
edges {
cursor
node {
name
}
}
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
}
}

Expected errors
In the GraphQL errors chapter, we discussed errors appearing in the
GraphQL response, typically arising from unexpected issues such as
a database not being reachable or bugs, but they are not well suited
for expected errors. Expected errors are situations that the client
wants to handle specifically. In this section we’ll demonstrate best
practice for managing expected errors.

Some typical examples of expected errors:

Payment details are invalid


New user attempts to sign up with an existing user’s email
address
A notoriously unreliable external system is not available

If the client wants to react to these situations, GraphQL errors are


not great because they exist outside the normal response data and
are untyped.

For example, an error for invalid payment details could look like
this.
{
"data": {
"makePayment": null
},
"errors": [{
"message": "Payment failed",
"extensions": {
"classification": "PAYMENT_ERROR",
"details": "Invalid credit card"
}
}]
}

A client now has to parse the “message” and potentially also look at
the “extensions”, which are untyped and can contain any data.

Imagine a more complex query where the response contains partial


data and some errors. It would be even harder to parse and handle
the error correctly.

These shortcomings of GraphQL errors led to the idea of modeling


expected errors in the schema. This makes it part of the typed API
contract and allows a client to handle them much more safely. The
cost we have to pay is a slightly more complex schema, as we will
see in the next example.

Example:
type Mutation {
makePayment(input : MakePaymentInput!): MakePaymentPayload
}

type MakePaymentPayload {
payment: Payment
error: MakePaymentError
}

enum MakePaymentError {
CC_INVALID,
PAYMENT_SYSTEM_UNAVAILABLE
}

This brings the payment errors into the response type, which
appears in the data section of the GraphQL response. Note how
these errors are no longer in the errors section of the GraphQL
response.
{
"data": {
"makePayment": {
"payment": null,
"error": "CC_INVALID"
}
}
}

For mutations, the Payload type is a natural place for mutation-


specific errors. For queries, we can use a union type to allow for
normal results or errors.
type Query {
pet(id: ID!): PetLookup
}
union PetLookup = Pet | PetLookupError

type Pet {
# Your Pet fields here
}

type PetLookupError {
# Your PetLookupError fields here
}

We can then use inline fragments to handle the result and error
cases.
query myPet {
pet(id: "123") {
... on Pet {
# Your Pet fields here
}
... on PetLookupError {
# Your PetLookupError fields here
}
}
}

Mutation format
The GraphQL community mostly uses a specific format for
mutation, which comes originally from Relay.

Name the field <verb><Entity>.


The field has a single argument input: <verb><Entity>Input!.
The type of the mutation response is <verb><Entity>Payload

For example, a mutation to create a user:


type Mutation {
createUser(input : CreateUserInput!): CreateUserPayload
}
Naming standards
The GraphQL community has largely come to a consensus on
schema naming standards. It’s good to adhere to these standards to
build consistent schemas that also align with the overall GraphQL
community.

Fields, input fields, and argument names are “camelCase”:


userName
Types are “PascalCase”: UserName
Enum values are capitalized “SNAKE_CASE”: FIRST_NAME

You might have noticed we have followed these standards


throughout the book.

In this chapter we covered key principles and best practices from


our experiences running GraphQL services. We hope this chapter
helps you design your own production ready GraphQL schemas.
DataFetchers in depth
In this chapter we will build on the earlier DataFetchers chapter and
discuss more advanced details, including how to make use of global
and local context, and reactive patterns.

More DataFetcher inputs


In the first DataFetchers chapter we discussed inputs to Spring for
GraphQL’s schema mapping controller methods, such as source
(parent) objects, arguments, and more. In this section, we’ll discuss
two additional inputs, global context and local context.

Global context
In GraphQL Java, GraphQLContext is a mutable map containing
arbitrary data, which is made available to every DataFetcher. It
provides a “global context” per execution. In Spring for GraphQL,
the global GraphQLContext can be accessed by adding it as a method
parameter to a schema mapping handler or batch mapping method.
In pure GraphQL Java, it can be accessed via
ExecutionInput.getGraphQLContext(). For example, let’s say we want to
make a “userId” accessible to every DataFetcher. In Spring for
GraphQL, we can access GraphQLContext via a schema mapping or
batch mapping method parameter and add a userId:
@SchemaMapping
MyType myField( GraphQLContext context) {
context.put( "userId", 123);
// Your logic here
}

It’s also possible to access and add to GraphQLContext via


ExecutionInput. As we saw in the Requests chapter, Spring for
GraphQL provides an interface for intercepting requests and
accessing ExecutionInput.
package myservice. service;

import org. springframework. graphql. server. WebGraphQlInterceptor;


import org. springframework. graphql. server. WebGraphQlRequest;
import org. springframework. graphql. server. WebGraphQlResponse;
import org. springframework. stereotype. Component;
import reactor. core. publisher. Mono;

@Component
class UserIdInterceptor implements WebGraphQlInterceptor {

@Override
public Mono< WebGraphQlResponse> intercept(
WebGraphQlRequest request, Chain chain
) {
request.configureExecutionInput(( executionInput, builder) ->
executionInput
.getGraphQLContext()
.put( "userId", "123");
return executionInput;
});

return chain.next( request);


}

Retrieve the userId value by using the @ContextValue parameter,


which retrieves a specific value from the GraphQLContext in schema
mapping or batch mapping handlers.
@SchemaMapping
MyType myField( @ContextValue String userId) {
// Your logic here
}

In pure GraphQL Java, we can add userId to GraphQLContext via


ExecutionInput or via DataFetchingEnvironment:

ExecutionInput executionInput = ...;


executionInput.getGraphQLContext().put( "userId", "123");

Note: the capitalisation of “L” is slightly different for the getter in


DataFetchingEnvironment.

DataFetcher df = ( env) -> {


env.getGraphQlContext().put( "userId", "123");
...
}

Retrieve the userId value via the DataFetchingEnvironment.


DataFetcher df = ( env) -> {
String userId = env.getGraphQlContext().get( "userId");
...
}

Local context
It’s also possible to set local context which only provides data to
child DataFetchers, rather than changing global context.

A GraphQL request is a tree of fields and every field has an


associated DataFetcher. Child DataFetchers are only invoked after
the current DataFetcher finishes. We’ll discuss the tree of fields
concept in more detail in the Execution chapter. Therefore, we can
ensure that information set in local context will only be made
accessible to child DataFetchers.
We can set the local context by returning a new DataFetcherResult
where localContext is not null. We discussed how DataFetcherResult
can be used to return data and errors in the Errors chapter. Now
we’ll show how it can also be used to set the local context.

For example, we have the following schema for customers and their
orders.
type Query {
order: Order
customerById(id: ID!): Customer
}

type Order {
id: ID
customer: Customer
}

type Customer {
id: ID
contact: Person
}

type Person {
name: String
}

We can directly query a customer as a root field or via an order.


query customerDetails {
customerById(id: "ID-1") {
contact {
name
}
}
}

or
query orderDetails {
order {
customer {
contact {
name
}
}
}
}

Add the following Java classes.


package myservice. service;

record Order( String id, String customerId) {


}

package myservice. service;

record Customer( String id, String contactId) {


}

package myservice. service;

record Person( String name) {


}

Imagine that our persistence layer stores the full customer next to
the order. That means, when we load an order, we have already
loaded the full customer including their contact information.
However, in our persistence layer, a customer loaded directly does
not include the corresponding contact information.

For queries including the order field, we can avoid a second fetch for
customer contact information by setting the local context when
loading the order and make use of it in the customer contact
DataFetcher.

In Spring for GraphQL, local context must be a GraphQLContext


object, set by returning a DataFetcherResult in the Query.order
DataFetcher. Note that this local instance of GraphQLContext is local
to a DataFetcher and its children, it is different to the instance of
GraphQLContext available globally.
We can retrieve the local context from the DataFetchingEnvironment.
If the Person is in the context, we can reuse the information.
Otherwise, we’ll make a request to the Person service for contact
information.

In the example below, we have used placeholder data retrieval


methods. Replace these methods with your logic. If you prefer to
quickly test this end-to-end, you can mock these methods with in-
memory objects.
package myservice. service;

import graphql. GraphQLContext;

import graphql. execution. DataFetcherResult;


import graphql. schema. DataFetchingEnvironment;
import org. springframework. graphql. data. method. annotation
.Argument;
import org. springframework. graphql. data. method. annotation
.QueryMapping;
import org. springframework. graphql. data. method. annotation
.SchemaMapping;
import org. springframework. stereotype. Controller;

@Controller
record OrderController( OrderService orderService,
PersonService personService) {

@QueryMapping
DataFetcherResult< Order> order() {
Order order = orderService.getOrder();
Person personForContact = order.getPersonForContact();
// Local instance of GraphQLContext
GraphQLContext localContext = GraphQLContext.newContext()
.put( "personForContact", personForContact)
.build();
// Return data and a new local context
return DataFetcherResult.< Order>newResult()
.data( order)
.localContext( localContext)
.build();
}
@SchemaMapping
Customer customer( Order order) {
return orderService.getCustomer( order);
}

@SchemaMapping
Person contact( Customer customer, DataFetchingEnvironment env) {
GraphQLContext localContext = env.getLocalContext();
if ( localContext != null
&& localContext.get( "personForContact") instanceof Person) {
return localContext.get( "personForContact");
}
return personService.getPerson( customer.contactId());
}

@QueryMapping
Customer customerById( @Argument String id) {
return orderService.getCustomerById( id);
}

In this example, the Person is not guaranteed to be set in the local


context. If a query is for customer details only, without an order,
there will be no personForContact in the local context. As we cannot
be sure if personForContact will be in the local context, we must
access the local GraphQLContext via the DataFetchingEnvironment and
then check if a Person has been set under the personForContact key.

If you are certain that the local context will always contain a
particular key, you can pass in a parameter to the schema mapping
method, annotated with @LocalContextValue. In this example, we
could not use this annotation, as a runtime exception would be
raised whenever personForContact is not set.

In pure GraphQL Java, the local context is also set via a


DataFetcherResult in the Query.order DataFetcher. If using GraphQL
Java without Spring for GraphQL, the object inserted into local
context can be of any type, and does not have to be an instance of
GraphQLContext.

// DataFetcher for Query.order


OrderService orderService;
DataFetcher< DataFetcherResult< Order>> orderDf = ( env) -> {
Order order = orderService.getOrder();
Person personForContact = order.getPersonForContact();
// Return data and a new local context
return DataFetcherResult.< Order>newResult()
.data( order)
.localContext( personForContact)
.build();
};

Then the DataFetcher for Customer.contact can make use of the pre-
loaded Person in local context, if it is available. If the Person is not
available, a request to the Person service will be made.
// DataFetcher for Customer.contact
PersonService personService;
DataFetcher< Person> contactDf = ( env) -> {
// If we already loaded the person earlier
if ( env.getLocalContext() instanceof Person) {
return env.getLocalContext();
}
Customer customer = env.getSource();
return personService.getPerson( customer.getContactId());
};

DataFetcher implementation patterns


There are a few considerations when implementing a DataFetcher:

Does it involve I/O? (e.g. HTTP calls to another service)


Is it computationally intensive?
Should it be reactive or not?

We will discuss these three patterns and when to use them. As the
next few examples demonstrate DataFetcher patterns, we will show
snippets rather than a full Spring for GraphQL controller.

Non-reactive DataFetcher

A non-reactive DataFetcher can involve blocking I/O or


computation work. Regardless of the type of work, the structure is
the same.
@Controller
record ThingController( DoSomeThing service) {
@SchemaMapping
MyType myField() {
return service.doSomething();
}
}

Or the same DataFetcher in pure GraphQL Java:


DoSomeThing service;
DataFetcher< MyType> myField = ( env) -> {
return service.doSomething();
};

This is the most straightforward pattern. If you are developing a


non-reactive service, this is the only pattern you need.

In a reactive service, this is still a valid option if the work is only fast
computation work, meaning no I/O is involved. The exact definition
of “fast” is domain-specific, but as a rough guide, “fast” would be
work that takes less than one millisecond to complete.

Wrapping blocking I/O


If the DataFetcher involves blocking I/O, we can offload the
blocking call to another thread.

In pure GraphQL Java, it could look like:


Executor threadPool;
DataFetcher< CompletableFuture< MyType> df = ( env) -> {
return CompletableFuture.supplyAsync(
() -> client.blockingCall(),
threadPool);
};

Although wrapping an I/O call does not make the whole service
completely reactive, it may still be worth doing as it doesn’t block
GraphQL Java itself and allows for parallel fetching of fields.

In Spring for GraphQL, we recommend using a reactive approach


with Reactor rather than using Java’s CompletableFuture. As we’ll see
later in this chapter, Reactor DataFetchers in Spring for GraphQL
are available without any additional code or configuration. Using
Reactor DataFetchers also enables the use of Reactor context.

Reactive I/O

A reactive DataFetcher usually involves using a reactive library such


as Async Http Client or Spring WebClient. While the library details
may vary, essentially it involves calling the library and returning a
CompletableFuture.

ReactiveClient client;
DataFetcher< CompletableFuture< Something> df = ( env) -> {
return client.call();
};

This pattern should be used in a reactive service every time I/O is


involved.

Reactive compute work

Reactive compute work requires a bit more effort compared to the


previous example, as it requires offloading the actual work onto
another thread.
The main pattern looks like this:
Executor threadPool;
DataFetcher< CompletableFuture< Something> df = ( env) -> {
return CompletableFuture.supplyAsync(
() -> client.call(),
threadPool);
};

This ensures that the compute-intense work is completed in a


separate thread, so it does not block GraphQL Java.

In Spring for GraphQL, we recommend using a reactive approach


with Reactor rather than using Java’s CompletableFuture, we’ll
discuss this later in this chapter.

Reactive or not?
Whether to use reactive patterns is a general question, which is not
specific to GraphQL. Here are some high-level considerations to
keep in mind.

The main tradeoff is between improved scalability or more


complicated code. A reactive service is more stable and predictable
under load than a non-reactive one. If you are going to run a service
with a high load that also needs to be very stable, reactive is our
recommendation.

However, it comes with the cost of maintaining and running a


reactive code base. Reactive is not a concept inherent to the Java
language itself. The concept was added later via CompletableFuture
and other libraries such as Reactor. The cost is code that is not as
simple to read, write, and debug as “normal” Java code.

A critical consideration is that everything must be reactive in order


to achieve the full benefits of a reactive code base. This means that
if your HTTP client inside a DataFetcher is not reactive, you can’t
make that DataFetcher fully reactive. The following example may
seem to make sense at first glance, but it is not a suitable solution.
RestTemplate restTemplate;
URI url;
Executor threadPool;

DataFetcher< CompletableFuture< MyType> df = ( env) -> {


return CompletableFuture.supplyAsync(
() -> restTemplate.getForObject( url, MyType.class), // blocki
threadPool);
};

This DataFetcher returns a CompletableFuture, but it actually does a


blocking call via the Spring RestTemplate HTTP client, which is a
blocking call. Whilst this does not block the GraphQL Java engine
directly, there is still a thread being blocked by the .getObject call.
This means we will not achieve the full benefits of a reactive service.

Spring for GraphQL Reactor support


Spring WebFlux is an asynchronous and non-blocking framework
based on Reactor. You can use Spring WebFlux together with Spring
for GraphQL to build Reactor DataFetchers.

Spring for GraphQL supports the Reactor types Mono and Flux as
return values, which enables us to write reactive DataFetchers. If
the words Mono and Flux are new to you, please see the Reactor
documentation.

We recommend using the Reactor types Mono and Flux rather than
Java’s CompletableFuture with Spring for GraphQL to make use of
Reactor context. However, it is still possible to return
CompletableFuture values.
To use Spring WebFlux, include org.springframework.boot:spring-
boot-starter-webflux as a dependency. We previously walked
through how to use Spring WebFlux in the Building a GraphQL
service chapter.

No additional code nor configuration is required to make use of


Reactor. Simply write a DataFetcher that returns a Reactor type. For
example:
@SchemaMapping( type = "Foo", field = "bar")
Mono< String> bar() {
...
}

or

@SchemaMapping( type = "Foo", field = "bar")


Flux< String> bar() {
...
}

To see more examples of DataFetchers returning Reactor types, see


the examples in the Building a GraphQL service chapter.

One challenge when using Reactor with GraphQL Java is that


GraphQL Java itself is based on CompletableFuture. Spring for
GraphQL manages conversion between Reactor types and
CompletableFuture. To prevent the Reactor Context from being lost
between conversions to and from CompletableFuture, Spring for
GraphQL saves and restores the Reactor context across different
DataFetcher invocations.

For example, if we want to propagate a logging prefix via Reactor


context:
package myservice. service;

import org. springframework. graphql. server. WebGraphQlInterceptor;


import org. springframework. graphql. server. WebGraphQlRequest;
import org. springframework. graphql. server. WebGraphQlResponse;
p g p g g p q p Q p ;
import org. springframework. stereotype. Component;
import reactor. core. publisher. Mono;
import reactor. util. context. Context;

// Set initial values for the Reactor context


@Component
class WebInterceptor implements WebGraphQlInterceptor {

@Override
public Mono< WebGraphQlResponse> intercept( WebGraphQlRequest requ
Chain chain) {
return chain.next( request)
.contextWrite( Context.of( "loggingPrefix", "123"));
}

For more details about WebGraphQlInterceptor, see the intercepting


requests section in the Requests chapter.

Every DataFetcher and any other code called by a DataFetcher can


access the prefix.
@QueryMapping
Mono< String> foo() {
return Mono.deferContextual( contextView -> {
String loggingPrefix = contextView.get( "loggingPrefix");
return Mono.just( loggingPrefix);
});
}

In this chapter we covered more advanced details about


DataFetchers, including how to make use of global and local
context, and reactive patterns. We also discussed how to use
Reactor types with Spring for GraphQL.
Directives
Directives are a powerful feature of GraphQL that allows us to
declare any kind of additional data to a schema or document. This
data can be used to change runtime execution or type validation
behavior.

In this advanced chapter, we will discuss directives in depth and


demonstrate use cases for directives.

Schema and operation directives


There are two broad categories of directives, schema and operation
directives. Schema directives are used on schema elements, and
operation directives are used in operations within a GraphQL
document.

Schema and operation directives have a name starting with @,


followed by an optional list of arguments in parentheses. For
example, here is the built-in @deprecated schema directive which is
used to indicate deprecated schema elements.
type Query {
search: String @deprecated(reason: "Too slow, please use searchF
searchFast: String
}

Every directive has a schema definition. As @deprecated is a built-in


directive, this definition is automatically added by every GraphQL
implementation. You should not manually add this definition to
your schema.
directive @deprecated(reason: String = "No longer supported")
on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
| ENUM_VALUE

Schema and operation directive definitions have a name starting


with @, an optional list of arguments in parentheses, followed by on
and a list of allowed locations where it can be used, separated by |.
Schema and operation directive definitions are declared in the
schema.

The allowed locations will determine whether this directive is a


schema or operation directive. For example, a directive on a
FIELD_DEFINITION is a schema directive, whereas a directive on a FIELD
is an operation directive. We’ll discuss the full list of locations for
schema and operation directives later in this chapter when we
create our own directives.

The GraphQL spec defines four built-in directives: @skip, @include,


@deprecated and @specifiedBy. @skip and @include are operation
directives, whereas @deprecated and @specifiedBy are schema
directives. You can also create your own schema and operation
directives, which we’ll cover in this chapter.

Built-in directives
The GraphQL spec defines four built-in directives, which must be
supported by all GraphQL implementations. Built-in directives can
be used without being declared. Later in this chapter, we’ll see how
to declare and implement our own directives.

@skip and @include


@skipand @include are operation directives that allow us to skip or
include certain fields during execution.

You should not declare these built-in directives in your schema. To


illustrate how they can be used, this is how @skip and @include are
defined:
directive @skip(if: Boolean!)
on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
directive @include(if: Boolean!)
on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

The if argument is a Boolean indicating whether the @skip or


@include directive is active. The exclamation mark ! indicates that
this argument is non-nullable.

Skipping (or not including) a field is like a request that doesn’t


contain this field at all. These queries produce the same result:
query myPets {
pets {
name
}
}

# same as:
query myPets2 {
pets {
name
age @skip(if: true)
}
}

# same as:
query myPets3 {
pets {
name
age @include(if: false)
}
}
To be more useful, @skip and @include should be combined with
variables rather than hard coded booleans. For example, we could
include an experimental field based on a variable value:
query myQuery($someTest: Boolean!) {
experimentalField @include(if: $someTest)
}

@deprecated
@deprecated is a schema directive that can be used to mark fields,
enum values, input fields, and arguments as deprecated in the
schema. It provides a structured way to document deprecations. By
default, the introspection API filters out deprecated schema
elements.

As @deprecated is a built-in directive, you should not declare it in


your schema. To illustrate how @deprecated is used, this is how it is
defined:
directive @deprecated(reason: String = "No longer supported")
on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
| ENUM_VALUE

You can optionally provide a reason for deprecation, which will


appear in automatically generated documentation and tooling. The
default reason is “No longer supported”.

This is an example of how to mark fields and enums as deprecated


in the schema:
type Query {
search: String @deprecated(reason: "Too slow, please use searchF
searchFast: String
}

enum Format {
LEGACY @deprecated(reason: "Legacy format")
NEW
}

The @deprecated directive is used to automatically generate


documentation. For example, this is how the schema above appears
in the documentation tab of GraphiQL. Click on the book icon in the
top left corner of the page, as shown in the screenshot “Deprecated
documentation in GraphiQL”.

Deprecated documentation in GraphiQL

@specifiedBy
@specifiedBy allows us to provide a scalar specification URL to
describe the behavior of custom scalar types.

Custom scalars are a powerful feature of GraphQL which enables


the type system to be extended. Initially in the GraphQL
specification, custom scalars could only be defined in the schema by
name. For example:
scalar DateTime

And this is still a valid way to define custom scalars in a schema.


However, only a name in the schema is not enough to explain the
behaviour of custom scalars. For example, DateTime
implementations can vary across services, but they might both
contain a schema element with the same name DateTime. The
@specifiedBy directive was introduced later to provide a way to
clearly document the behavior of custom scalars. The provided URL
should link to a specification including data format, serialization,
and coercion rules. For the full details and specification templates,
see the GraphQL Scalars project.

We recommend using @specifiedBy to clearly describe your custom


scalar. However, using this directive with custom scalars is not
compulsory.

As @specifiedBy is a built-in directive, you should not declare it in


your schema. To illustrate how @specifiedBy is used, this is how it is
defined:
directive @specifiedBy(url: String!) on SCALAR

This is an example for a DateTime scalar:


scalar DateTime @specifiedBy(url:
"https://scalars.graphql.org/andimarek/date-time")

With the GraphQL Scalars project, you can create your own custom
scalars specifications and host them on the GraphQL Foundation’s
scalars.graphql.org domain, like the linked URL in the previous
example. You can also read and link to other contributed
specifications. See the GraphQL Scalars project for more
information.

Defining your own schema and operation


directives
Schema and operation directives have a name starting with @, an
optional list of arguments in parentheses, followed by on and a list
of allowed locations where it can be used. The allowed locations
determine whether the directive is an operation directive or schema
directive. When creating your own schema and operation directives,
they must be defined in the schema.

It’s important to understand that all custom schema and


operation directives don’t have any effect until we
implement the custom behavior. We’ll first discuss how to
define schema and operation directives, then how to implement
them.

Defining schema directives


Let’s walk through some examples. Let’s create an @important
directive. The directive can only be used on field definitions, which
makes it a schema directive:
# No arguments and can only be used on field definitions
directive @important on FIELD_DEFINITION

Our new @important directive can be used to indicate certain schema


fields are important.
type Query {
hello: String @important # usage of the directive
}

We have defined the @important directive to only be allowed on fields


inside the schema. Other locations will be invalid, for example:
# No arguments and can only be used on field definitions
directive @important on FIELD_DEFINITION

type Query @important { # Invalid usage


hello: String
}

By using the directive on the Query type, we have created an invalid


schema.

To make this a valid schema, we could add another location to the


directive definition. Provide multiple locations by separating them
with |.
# Can be used in two locations
directive @important on FIELD_DEFINITION | OBJECT

type Query @important { # Now it is valid


hello: String
}

All custom schema and operation directives don’t have any effect
until we implement new custom behavior. The @important directive
won’t have any effect until we implement new logic, which we’ll
cover later in this chapter. This differs from the built-in directives,
which all have a well-defined effect.

The difference between schema directives and operation directives


is the list of allowed locations. Here is an example of a schema
directive @foo with all possible eleven locations in a schema.
type @foo on SCHEMA | SCALAR | OBJECT |
FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE |
| UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION

schema @foo { # Schema


query : Query
}

scalar CustomScalar @foo # Scalar

type Query @foo { # Object


hello(
arg: SomeInput @foo # Argument
): String @foo # Field definition
}

interface SomeInterface @foo { # Interface


hello: String
}

type SomeImplementation implements SomeInterface {


hello: String
}

union SomeUnion = SomeImplementation @foo # Union

enum SomeEnum @foo { # Enum


ENUM_VALUE @foo # Enum value
}

input SomeInput @foo { # Input object


inputField: String @foo # Input field
}

Whilst it is technically possible to define a directive that includes


locations for both schema and operation directives, in practice this
is not common.

Defining operation directives


Let’s define an operation directive @cache, which can be used on
operation fields. Note that FIELD in the example below refers to
operation fields, and FIELD_DEFINITION used in the previous schema
directive example refers to schema fields.
# Can only be used on a field in a GraphQL document
directive @cache on FIELD

type Query {
pet: Pet
}

type Pet {
name: String
lastTimeOutside: String
}

We can only use this @cache directive on fields in a GraphQL


document, which contains operations.
query myPet {
pet {
name
lastTimeOutside @cache
}
}

Directives can also have arguments. Let’s add a maxAge argument,


with a default value of 1000.
# Argument with a default value
directive @cache(maxAge: Int = 1000) on FIELD

In a GraphQL document, we could use our updated @cache directive


to specify a maxAge value:
query myPet {
pet {
name
lastTimeOutside @cache(maxAge: 500)
}
}
All custom schema and operation directives don’t have any effect
until we implement new custom behavior. For example, the
operation above where lastTimeOutside has a @cache directive
behaves exactly the same as without it, until we have implemented
some new logic. We’ll demonstrate implementation of behavior for
directives later in this chapter. You don’t need to define behaviour
for the built-in directives, which all have a well-defined effect that is
implemented by every GraphQL implementation.

The difference between schema directives and operation directives


is the list of allowed locations. Here is an operation directive with
all possible eight locations in a GraphQL document, which contains
operations.
type @foo on QUERY | MUTATION | SUBSCRIPTION |
FIELD | FRAGMENT_DEFINITION | FRAGMENT_SPREAD |
INLINE_FRAGMENT | VARIABLE_DEFINITION

query someQuery(
$var: String @foo # Variable definition
) @foo # Query
{
field @foo # Field
... on Query @foo { # Inline fragment
field
}
...someFragment @foo # Fragment spread
}

fragment someFragment @foo { # Fragment


field
}

mutation someMutation @foo { # Mutation


field
}

subscription someSubscription @foo { # Subscription


field
}
Although it is technically possible to define a directive that includes
locations associated with schema and operation directives, in
practice this is not common.

Repeatable directives
We can define schema and operation directives as repeatable,
enabling it to be used multiple times in the same location. If
repeatable is not included in the directive definition, the directive
will be non-repeatable by default.

For example, a repeatable schema directive @owner:


directive @owner(name: String!) repeatable on FIELD_DEFINITION

type Query {
# Multiple owners per field possible
hello: String @owner(name: "Brian") @owner(name: "Josh")
}

Implementing logic for schema directives


To create a new schema directive, we have to define the directive
and implement the logic for it.

As this is an advanced chapter, the code examples which follow are


more complicated and involve schema traversal and transformation.

Changing execution logic with schema


directives
Let’s implement the logic for a new @important directive, which
indicates which schema fields are important, and the reason for its
importance. It is defined as:
directive @important(reason: String!) on FIELD_DEFINITION

And could be used in a schema on a field:


type Query {
hello: String @important(reason: "Being friendly")
}

To explain how schema directive definition and usage are


represented in code, we will walk through sample code with pure
GraphQL Java. Then we’ll wrap up this section with an
implementation for @important in Spring for GraphQL.

Schemas are represented as an instance of GraphQLSchema inside


GraphQL Java. A GraphQLSchema instance will contain a
GraphQLDirective instance representing a schema directive’s
declaration.

In pure GraphQL Java, we could access this GraphQLDirective


instance via GraphQLSchema.getDirective, which contains the name,
arguments, valid locations, and whether the directive is repeatable.
In this example schema is the name of an instance of GraphQLSchema.
GraphQLDirective importantDirective = schema.getDirective( "importan
String name = importantDirective.getName();
List< GraphQLArgument> arguments = importantDirective.getArguments(
boolean repeatable = importantDirective.isRepeatable();
EnumSet< Introspection.DirectiveLocation> directiveLocations =
importantDirective.validLocations();

GraphQLDirective represents the definition of a directive, and


GraphQLArgument represents an argument definition.

Then we have an instance of GraphQLAppliedDirective, which


represents the usage of the directive in a schema. In our example,
we only have one usage.
In pure GraphQL Java, usage of our schema directive can be
accessed via our instance of GraphQLSchema called schema:
GraphQLSchema schema = ...;
GraphQLObjectType query = schema.getObjectType( "Query");
GraphQLFieldDefinition hello = query.getFieldDefinition( "hello");
GraphQLAppliedDirective appliedDirective
= hello.getAppliedDirective( "important");
GraphQLAppliedDirectiveArgument reason
= appliedDirective.getArgument( "reason");
String reasonValue = reason.getValue(); // "Being friendly"

In our example, the GraphQLAppliedDirective contains one


GraphQLAppliedDirectiveArgument for our one argument “reason” with
the value “Being friendly”. To highlight the difference, an instance
of GraphQLDirective has GraphLArguments, and GraphQLAppliedDirective
has GraphQLAppliedDirectiveArguments. GraphQLDirective represents
the definition, and GraphQLAppliedDirective represents the usage of
the directive in the schema.

In practice, we often use schema directives inside a DataFetcher.


For example, we could use the @important directive like this in a
Spring for GraphQL DataFetcher to change how important fields are
handled:
package myservice. service;

import graphql. schema. DataFetchingEnvironment;


import graphql. schema. GraphQLAppliedDirective;
import graphql. schema. GraphQLFieldDefinition;
import org. springframework. graphql. data. method. annotation
.QueryMapping;
import org. springframework. stereotype. Controller;

@Controller
class GreetingController {
@QueryMapping
String hello( DataFetchingEnvironment env) {
GraphQLFieldDefinition fieldDefinition = env.getFieldDefinitio
GraphQLAppliedDirective important
= fieldDefinition.getAppliedDirective( "important");
if ( important != null) {
if ( important != null) {
return handleImportantFieldsDifferently( env);
}
return "Hello";
}
}

Validation with schema directives


Directives can also be used for validation. For example, a @size
schema directive for arguments, which enforces a minimum
quantity.
directive @size(min : Int = 0) on ARGUMENT_DEFINITION

It could be applied to an argument definition to validate if there are


enough Applications in the input:
type Query {
hired(applications : [Application!] @size(min : 3)) : [Boolean]
}

Validation is such a useful and commonly requested idea that there


is an extended validation library for GraphQL Java, which is
maintained by the GraphQL Java team.

To use the validation directives in the graphql-java-extended-


validation library, add the package.

For Gradle, add this to your build.gradle file:


implementation 'com.graphql-java:graphql-java-extended-validation:19

For Maven:
<dependency >
<groupId >com.graphql-java</groupId >
<artifactId >graphql-java-extended-validation</artifactId >
<version >19.1</version >
</dependency >

Note: the major version number corresponds to the linked major


version of the main GraphQL Java release. At the time of writing,
the latest version of Spring for GraphQL 1.1.2, uses GraphQL Java
19.

To wire these validation directives in Spring for GraphQL, create a


RuntimeWiringConfigurer bean. This will add a default selection of
directive implementations from graphql-java-extended-validation.
You should separately define the directives for your service in your
schema.
@Configuration
class GraphQlConfig {
@Bean
RuntimeWiringConfigurer runtimeWiringConfigurer() {
// Adds all default validation rules in library
ValidationRules possibleRules
= ValidationRules.newValidationRules().build();
// ValidationSchemaWiring implements SchemaDirectiveWiring
ValidationSchemaWiring validationDirectiveWiring
= new ValidationSchemaWiring( possibleRules);
return wiringBuilder -> wiringBuilder
.directiveWiring( validationDirectiveWiring);
}
}

The Spring Boot starter automatically detects all


RuntimeWiringConfigurer beans.

Adding metadata with schema directives


Another very common use case of schema directives is providing
metadata, which does not change execution but is only relevant for
the schema itself.
For example, we work in a large team and we want to document the
ownership of certain types with an @owner directive:
directive @owner(name: String) on OBJECT

type User @owner(name: "Antoine") {


# More fields here
}

type Order @owner(name: "Felipe") {


# More fields here
}

type Payment @owner(name: "Stephan") {


# More fields here
}

A significant benefit of using directives is the ability to process them


programmatically. For example, we could create an automatic
report, showing which types are owned by whom. This ownership
report example is more like a script rather than part of a Spring for
GraphQL service. Therefore, we’ll demonstrate this example in pure
GraphQL Java.

GraphQL Java provides tools to programmatically visit schema


elements. In pure GraphQL Java code, we can map owners to types
with the SchemaTraverser class, together with a GraphQLTypeVisitor. In
this example, we will visit GraphQLObjectTypes. There are many more
options and hooks to customize visitors in GraphQL Java.
GraphQLSchema schema = ...;
Map< String, List< GraphQLObjectType>> ownerToTypes
= new LinkedHashMap<>();

SchemaTraverser schemaTraverser = new SchemaTraverser();


schemaTraverser.depthFirstFullSchema(new GraphQLTypeVisitorStub
@Override
public TraversalControl visitGraphQLObjectType(
GraphQLObjectType objectType,
TraverserContext< GraphQLSchemaElement> context
) {
GraphQLAppliedDirective directive

= objectType.getAppliedDirective( "owner");
if ( directive != null) {
String owner = directive.getArgument( "name").getValue();
ownerToTypes.putIfAbsent( owner, new ArrayList<>());
ownerToTypes.get( owner).add( objectType);
}
return TraversalControl.CONTINUE;
}
}, schema);

We visit every GraphQLObjectType in the schema, and check the owner


of each object type. We then assemble a map of the owner to a list
of object types.

This @owner example was more like a script rather than core
functionality in a Spring for GraphQL service. However, if you want
to traverse a schema in Spring for GraphQL, you can register
graphql.schema.GraphQLTypeVisitor via the GraphQlSource.builder with
builder.schemaResources(..).typeVisitors(..).

Taking a step further, we can even change the global GraphQLSchema


with schema directives. For example, we could automatically add a
suffix to every field based on a directive.
directive @suffix(name: String) on OBJECT

type Dog @suffix(name:"__bark") {


name: String
}

type Cat @suffix(name: "__meow") {


name: String
}

With pure GraphQL Java, we can make use of schema transformer


and type visitor tools.
GraphQLSchema newSchema = SchemaTransformer.transformSchema(
schema, new GraphQLTypeVisitorStub() {

@Override
public TraversalControl visitGraphQLFieldDefinition(
GraphQLFieldDefinition fieldDefinition,
TraverserContext< GraphQLSchemaElement> context
) {
GraphQLSchemaElement parentNode = context.getParentNode();
if (!( parentNode instanceof GraphQLObjectType)) {
return TraversalControl.CONTINUE;
}
GraphQLObjectType objectType = ( GraphQLObjectType) parentNode;
GraphQLAppliedDirective directive = objectType
.getAppliedDirective( "suffix");

if ( directive != null) {
String suffix = directive.getArgument( "name").getValue();
GraphQLFieldDefinition newFieldDefinition
= fieldDefinition.transform( builder
-> builder.name( fieldDefinition.getName() + suffix));
return changeNode( context, newFieldDefinition);
}

return TraversalControl.CONTINUE;
}
});

We are using the SchemaTransformer class to change a schema whilst


traversing it. SchemaTransformer leverages the same
GraphQLTypeVisitor as the SchemaTraverser used in the previous
example.

We visit every field definition and try to get the object containing
the field via context.getParentNode(). Then we get the
GraphQLAppliedDirective for the suffix. We use this to create a
GraphQLFieldDefinition with the changed name. The last thing to do
is to call changeNode (from GraphQLTypeVisitor) which actually
changes the field.
To use this same schema transformation example in Spring for
GraphQL, register a graphql.schema.GraphQLTypeVisitor via the
GraphQlSource.Builder with
builder.schemaResources(..).typeVisitorsToTransformSchema(..).

A word of caution: as you can see from this code example,


transforming a schema is not trivial. Be careful not to inadvertently
create an invalid schema during schema transformation. To view a
more complex example, please see graphql.util.Anonymizer in
GraphQL Java. This is a utility to help users of GraphQL Java
anonymize their schemas to provide realistic examples when
reporting issues or suggesting improvements to the maintainer
team.

Implementing logic for operation


directives
Operation directives are used with GraphQL operations. Note that
this concept is also often referred to as “query” directives, although
this type of directive can be used on all three operations: queries,
mutations, and subscriptions.

Let’s implement the logic for a @cache operation directive:


directive @cache(maxAge: Int) on FIELD

This is an operation directive that enables clients to specify how


recent cache entries must be. This is an example of an operation
directive that can change execution.

For example, a client specifies that hello cache entries must not be
older than 500 ms, otherwise we re-fetch these entries.
query caching {
hello @cache(maxAge: 500)
}
In GraphQL Java, operation directive definitions are represented as
GraphQLDirectives. Operation directive usages are represented as
QueryAppliedDirectives. Note that the word “query” here is
misleading, as it actually refers to a directive that applies to any of
the three GraphQL operations: queries, mutations, or subscriptions.
Operation directives are still commonly referred to as “query”
directives, hence the class name.

To explain how operation directive definition and usage are


represented in code, we will walk through sample code with pure
GraphQL Java. Then we’ll wrap up this section with an
implementation for @cache in Spring for GraphQL.

In pure GraphQL Java, we can access an operation directive’s


definition in the schema via an instance of GraphQLSchema, in this
example called schema. We can access the operation directive’s name,
arguments, locations, and whether it is repeatable.
GraphQLSchema schema = ...;
GraphQLDirective cacheDirective = schema.getDirective( "cache");
String name = cacheDirective.getName();
List< GraphQLArgument> arguments = cacheDirective.getArguments();
boolean repeatable = cacheDirective.isRepeatable();
EnumSet< Introspection.DirectiveLocation> directiveLocations
= cacheDirective.validLocations();

Usages of operation directives are represented in GraphQL Java as


instances of QueryAppliedDirective, and provided argument values
are represented as QueryAppliedDirectiveArgument.

We can access operation directives usage during execution via


getQueryDirectives() in DataFetchingEnvironment. For example:

package myservice. service;

import graphql. execution. directives. QueryAppliedDirective;


import graphql. execution. directives
.QueryAppliedDirectiveArgument;
import graphql. execution. directives. QueryDirectives;
import graphql. schema. DataFetchingEnvironment;
import org. springframework. graphql. data. method. annotation
.QueryMapping;
import org. springframework. stereotype. Controller;

import java. util. List;

@Controller
class GreetingController {

@QueryMapping
String hello( DataFetchingEnvironment env) {
QueryDirectives queryDirectives = env.getQueryDirectives();
List< QueryAppliedDirective> cacheDirectives = queryDirectives
.getImmediateAppliedDirective( "cache");
// We get a List, because we could have
// repeatable directives
if ( cacheDirectives.size() > 0) {
QueryAppliedDirective cache = cacheDirectives.get( 0);
QueryAppliedDirectiveArgument maxAgeArgument
= cache.getArgument( "maxAge");
int maxAge = maxAgeArgument.getValue();

// Now we know the max allowed cache time and


// can make use of it
// Your logic here
}
// Your logic here
}

In this chapter, we covered directives, a powerful GraphQL feature


that enables us to change runtime execution and type validation.
We covered the built-in directives and how to write our own
directives.
Execution
Execution is handled by the GraphQL Java engine. In this advanced
chapter, we will look under the hood into how GraphQL Java
executes a request based on a schema. By the end of this chapter,
you’ll have a deeper understanding of how requests are executed by
the GraphQL Java engine.

Initializing execution objects


Before executing any GraphQL requests, Spring for GraphQL has to
understand the schema and relevant configuration. To achieve this,
Spring for GraphQL initializes an instance of GraphQL Java’s
graphql.GraphQL class, which contains all the objects needed to
execute a GraphQL operation, including the schema and execution
strategies. Usually only one instance of graphql.GraphQL is initialized
for all requests.

As part of the graphql.GraphQL initialization process, Spring for


GraphQL loads schema files, exposes relevant properties, detects
RuntimeWiringConfigurer beans and more. See the Spring for
GraphQL documentation for detailed information.

While Spring for GraphQL initializes graphql.GraphQL, if there is


anything you want to change, the instance can be accessed and
modified via the GraphQlSource contract. For example, to add a
custom execution ID provider, create a
GraphQlSourceBuilderCustomizer bean:
package myservice. service;

import org. springframework. boot. autoconfigure. graphql


.GraphQlSourceBuilderCustomizer;
import org. springframework. context. annotation. Bean;
import org. springframework. context. annotation. Configuration;

@Configuration
class GraphQlConfig {
@Bean
GraphQlSourceBuilderCustomizer sourceBuilderCustomizer() {
return ( builder) ->
builder.configureGraphQl( graphQlBuilder ->
// Here we can use `GraphQL.Builder`
// For example, executionIdProvider
graphQlBuilder
.executionIdProvider(new MyExecutionIdProvid

}
}

Note that customizing the GraphQlSource is entirely optional. Spring


for GraphQL already initializes enough to start executing requests
without additional custom configuration.

If you are using GraphQL Java without Spring for GraphQL, this is
how to manually initialize the graphql.GraphQL object.

String sdl = "type Query { foo: String }"; // Your schema here
TypeDefinitionRegistry parsedSdl = new SchemaParser().parse( sdl);

DataFetcher< String> foo = ( env) -> "foo";


TypeRuntimeWiring queryWiring = newTypeWiring( "Query")
.dataFetcher( "foo", foo)
.build();
RuntimeWiring runtimeWiring = newRuntimeWiring()
.type( queryWiring)
.build();
GraphQLSchema schema = new SchemaGenerator()
.makeExecutableSchema( parsedSdl, runtimeWiring);
GraphQL graphQL = GraphQL.newGraphQL( schema).build();

How Spring for GraphQL starts execution


In the request and response chapter, we discussed how Spring for
GraphQL handles requests and responses over HTTP. In this
chapter, we will focus on the execution steps after a request is
received. These execution steps take place inside the GraphQL Java
engine.

Recall from the request and response chapter that Spring for
GraphQL automatically handles the HTTP protocol. A GraphQL
request is an HTTP POST encoded as application/json.

For every received HTTP request, Spring for GraphQL automatically


creates an instance of GraphQL Java’s ExecutionInput. Recall from
the request and response chapter that ExecutionInput is the GraphQL
Java object that represents a GraphQL request, without transport
concerns. Inside Spring for GraphQL, this happens in the
DefaultExecutionGraphQlService. It is possible to replace this with a
custom implementation of ExecutionGraphQlService.

Control then passes to GraphQL Java, which executes the GraphQL


request represented by an ExecutionInput instance. Following
execution, an ExecutionResult instance containing response data
and/or errors is returned. What happens in the GraphQL Java
engine between ExecutionInput and ExecutionResult is the focus of
the remainder of the chapter.

If you are using GraphQL Java without Spring for GraphQL, this is
how to manually create an ExecutionInput and execute the request.
GraphQL graphQL = GraphQL.newGraphQL( schema).build();

ExecutionInput request = ExecutionInput.newExecutionInput()


.query( "query test {foo}")
.build();

ExecutionResult result = graphQL.execute( request);

Execution steps
In GraphQL Java, execution includes the following steps:

1. parsing the “query” (document) value from the request as a


GraphQL document
2. validating the document
3. coercing variables
4. fetching data

These are the steps between the GraphQL Java engine receiving a
ExecutionInput request and returning a ExecutionResult instance with
data and/or errors.

Parsing and validation


Recall from the request and response chapter that the “query” in a
GraphQL request is actually a GraphQL document, which can
contain one or more operations. This “query” value is inserted into
an instance of GraphQL Java’s ExecutionInput, which represents a
GraphQL request.

The first step is parsing the “query” (document) value from the
ExecutionInput and validating it. If the document contains invalid
syntax, the parsing fails immediately. Otherwise, if the document is
syntactically valid, it then is validated against the schema.
Coercing variables
See the query language chapter for an overview of GraphQL
variables and see how variables are sent in an HTTP request in the
request and response chapter.

If the request contains variables, they need to be “coerced”. The


coercing process converts the variable values provided in the
request into an internal representation and also validates the
variables. For example:
query myQuery($name: String) {
echo(value: $name)
}

This query has one variable $name with the type String. If the request
now contains the following variables, variable coercing would fail
since we expect a single String for name, not a list of Strings.
{
"name": ["Luna", "Skipper"]
}

Fetching data
The last step is the core of execution: GraphQL Java fetching the
data needed to fulfill the request.

As detailed in the DataFetchers chapter, a DataFetcher in GraphQL


Java is a generic function that loads data for one specific field.
Spring for GraphQL implements DataFetchers via the @SchemaMapping
controller annotation and shortcut annotations such as
@QueryMapping.

Every field in the schema has a DataFetcher assigned to it.


DataFetchers associated with fields in a GraphQL request will be
invoked by GraphQL Java to fetch the data.
It is possible to for a field to accidentally not be mapped to any
DataFetcher, even after PropertyDataFetchers are generated. This
could happen by forgetting a @SchemaMapping annotation or missing a
Java property. A forgotten DataFetcher will be treated as
DataFetcher returning a null result. As you usually don’t want to
forget any DataFetchers, the forthcoming 1.2 version of Spring for
GraphQL will add schema mapping checks on startup.

Let’s look more closely at an example schema and query, which will
help us understand the overall execution algorithm.
type Query {
dogs: [Dog]
}

type Dog {
name: String
owner: Person
friends: [Dog]
details: DogDetails
}

type Person {
firstName: String
lastName: String
}

type DogDetails {
barking: Boolean
shedding: Boolean
}

query myDogs {
dogs {
name
owner {
firstName
lastName
}
friends {
name
}
details {
barking
shedding
}
}
}

GraphQL Java interprets every GraphQL operation as a tree of


fields. Each field has an associated DataFetcher. It is therefore
equally valid to describe an operation as a tree of DataFetcher
closures (unnamed functions).

Tree of Fields

This query, as shown in the diagram “Tree of Fields”, has three


levels, with dogs as the single root field. GraphQL Java traverses the
query breadth-first and invokes the corresponding DataFetcher
when visiting each field. Once a field’s DataFetcher has successfully
returned data, we invoke the DataFetcher for each of its children. So
the first DataFetcher being invoked is /dogs, followed by /dogs/name,
/dogs/owner, /dogs/friends and /dogs/details.

A critical detail is that GraphQL Java fetches the children of a field


in parallel, if possible.

For this example, let’s assume all DataFetchers allow parallel


execution. The next steps of the execution depend on the order the
DataFetchers finish. Let’s say /dogs/friends finishes first, followed
by /dogs/owner then /dogs/details. This leads us to the execution
order as shown in the diagram “Execution Order”.

Execution Order

In this diagram, the numbers show the order of execution. Fields


with the same number are executed in parallel.

To summarize, the execution is a breadth-first traversal of the


fields, with each field finishing when its DataFetcher completes. We
execute the children of a field in parallel, if possible.

Reactive concurrency-agnostic
GraphQL Java is reactive concurrency-agnostic. This means
GraphQL Java doesn’t prescribe a specific number of threads nor
when they are used during execution. This is achieved by leveraging
java.util.concurrent.CompletableFuture. Every DataFetcher can
return a CompletableFuture, or if not, GraphQL Java wraps the
returned value into a CompletableFuture.

This enables GraphQL Java to invoke all the child DataFetchers of a


field at once, similar to this:
// All happens in the same thread.
// GraphQL Java doesn't create a new thread
// or use a thread pool.
List< CompletableFuture> dataFetchersCFs
= new ArrayList< CompletableFuture>();
for ( DataFetcher df: childrenDFs) {
Object cf = df.get( env);
// Wrapping non-CF
if (!( cf instanceof CompletableFuture)) {
cf = CompletableFuture.completedFuture( cf);
}
dataFetchersCFs.add(( CompletableFuture) cf);
}

A key question is how much work does the DataFetcher


perform in the current thread? If no work or only very minimal
work is done in the current thread, then GraphQL Java itself works
as efficiently as possible. If a DataFetcher is using the current
thread (by either doing computation, or waiting for some I/O to
return), this blocks GraphQL Java itself, which then can’t invoke
another DataFetcher.

Here are a few DataFetcher examples to make this clear. Note that it
is equivalent in Spring for GraphQL to implement these
DataFetchers via controller methods annotated with @SchemaMapping.
// Intensive compute work in the current thread
DataFetcher< String> df = ( env) -> {
String str = intensiveComputeString();
return str;
};

// Still doing the actual work in the current thread


DataFetcher< CompletableFuture< String>> df = ( env) -> {
String str = intensiveComputeString();
return CompletableFuture.completedFuture( str);
};

// Blocking I/O call


DataFetcher< String> df = ( env) -> {
String str = makeHttpCallAndWaitForIt();
return str;
};

// Quick compute work in the current thread


DataFetcher< String> df = ( env) -> {
return "Hello";
};

// Offloaded compute work on another thread


DataFetcher< CompletableFuture< String>> df = ( env) -> {
Executor threadPool = ...;
return CompletableFuture.supplyAsync( () -> {
String str = intensiveComputeString();
return str;
}, threadPool);
};

// Offloaded blocking I/O calls to another thread


DataFetcher< CompletableFuture< String>> df = ( env) -> {
Executor threadPool = ...;
return CompletableFuture.supplyAsync( () -> {
String str = makeHttpCallAndWaitForIt();
return str;
}, threadPool);
};

// Using a reactive API to make an HTTP call


DataFetcher< CompletableFuture< String>> df = ( env) -> {
return makeReactiveHttpCall();
};

We recommend that a DataFetcher should never occupy the thread


it is called in, in order to achieve maximum efficiency.

Spring for GraphQL also enables DataFetchers to return Reactor


values, in addition to CompletableFuture, as we saw in the Reactor
support section in the DataFetchers Part 2 chapter.

Completing a field
After a DataFetcher returns a value for a field, GraphQL Java needs
to process it. This phase is called “completing a field”.

If the value is null, completing terminates and does nothing further.


If the field type is a list, we complete all elements inside the list,
depending on the generic type of the list.

For scalars and enums, the value is “coerced”. Coercing has two
different purposes: first is making sure the value is valid, the second
one is converting the value to an internal Java representation. Every
GraphQLScalarType references a graphql.schema.Coercing instance. For
enums, the GraphQLEnumType.serialize method is called.

For example, the built-in scalar Scalars.GraphQLInt only accepts Java


Number values.

This means if we have a DataFetcher for a field of type Int and it


returns the Boolean false, it would cause an error.
type Query {
someInt: Int
}

DataFetcher invalidDf = ( env) -> {


return true; // Will cause an error during execution
}

TypeResolver
If the type of the field is an interface or union, GraphQL Java needs
to determine the actual object type of the value via a TypeResolver.
See the DataFetchers chapter for an introduction to TypeResolvers
and how to use them in Spring for GraphQL and GraphQL Java.
This section focuses on the execution of TypeResolvers.

In our schema, we have a Pet interface:


type Query {
pet: Pet
}

interface Pet {
name: String
}

type Dog implements Pet {


name: String
barks: Boolean
}

type Cat implements Pet {


name: String
meows: Boolean
}

After a TypeResolver returns the type of the value, this information is


used to determine the actual sub-selection of this field that needs to
be fetched.

A sample query could look like:


query myPet {
pet {
...on Dog {
barks
}
...on Cat {
meows
}
}
}

Here the sub-selection for pet is { ...on Dog { barks } ...on Cat {
meows } }. If the returned value from the DataFetcher is a Dog, we
need to fetch the field barks; if it is a Cat, we need to fetch meows.

Then the DataFetcher for all the fields in the sub-selection are
called, as explained in the previous sections. This is a recursive step,
which then again leads to the completion of each of the fields.

Query vs mutation
Queries and mutations are executed in an almost identical way. The
only difference is that the spec requires serial execution for multiple
mutations in one operation.

For example:
mutation modifyUsers {
deleteUser( ... ) { ... }
addOrder( ... ) { ... }
changeUser( ... ) { ... }
}

vs
query getUsersAndOrders {
searchUsers( ... ) { ... }
userById( ... ) { ... }
allOrders { ... }
}

DataFetchers for the mutation are invoked serially. The DataFetcher


for the mutation field addOrder is only invoked after deleteUser
finishes. Likewise, the DataFetcher for changeUser is only invoked
after addOrder finishes. Contrast this behaviour to the query where
we invoke the DataFetchers in parallel for all three fields:
searchUsers, userById, and allOrders.

This is the only difference in execution between queries and


mutations. We implement a mutation as a DataFetcher, but there is
a rule to follow when using GraphQL Java. A mutation DataFetcher
may have side effects, whereas queries must not, although this is
not enforced by GraphQL Java.

In this advanced chapter, we took a deep dive into execution inside


the GraphQL Java engine. We also discussed how Spring for
GraphQL initializes execution objects, and how it starts execution in
GraphQL Java after receiving a request.
Instrumentation
Instrumentation is a general mechanism to hook into GraphQL
Java. You can inject code that can observe the execution of a query
and also change runtime behavior. Instrumentation is particularly
useful for performance monitoring and custom logging.

Instrumentation in Spring for GraphQL


The Spring Boot starter will automatically detect Instrumentation
beans as part of the graphql.GraphQL initialization process.
graphql.GraphQL is a key GraphQL Java class containing information
necessary to execute a request.

Let’s add our first instrumentation, MaxQueryDepth. This


instrumentation will detect when an operation’s depth is above the
specified number and abort execution early. You may want to add
this instrumentation to enforce a hard limit on operation depth, as a
way to limit resource use.

For example, imagine a query that requests for the names of


friends, and in turn, the names of their friends, and so on. The sheer
depth of this query may result in the service spending considerable
resources to complete the request.
query veryDeep {
hero {
name
friends {
name
friends {
name
friends {
name
# And so on!
}
}
}
}
}

For convenience, GraphQL Java includes a few built-in


instrumentations, which are listed later in this chapter. Let’s add
the built-in MaxQueryDepth instrumentation as a bean to our Spring
for GraphQL service, and set the maximum depth to an appropriate
number. This instrumentation will be automatically detected and
registered by the Spring Boot starter, there is no further
configuration required.
package myservice. service;

import graphql. analysis. MaxQueryDepthInstrumentation;


import org. springframework. context. annotation. Bean;
import org. springframework. context. annotation. Configuration;

@Configuration
class MyGraphQLConfiguration {
@Bean
MaxQueryDepthInstrumentation maxQueryDepthInstrumentation() {
return new MaxQueryDepthInstrumentation( 15);
}
}

When an operation is requested with a depth of greater than the


specified value, the execution is aborted early and the following
error message is returned within the response.
{
"errors": [
{
"message": "maximum query depth exceeded 42 > 15",
"extensions": {
"classification": "ExecutionAborted"
}
}
]
}

Writing a custom instrumentation


You can write a custom instrumentation using instrumentation
hooks available in the graphql.execution.Instrumentation interface in
GraphQL Java. Each hook is a separate method. A list of available
hooks is presented later in this chapter.

Let’s write a custom instrumentation that measures the time taken


to execute a GraphQL request. This is the time between the start of
GraphQL execution until the time the request is completed.
package myservice. service;

import graphql. ExecutionResult;


import graphql. execution. instrumentation. InstrumentationContext;
import graphql. execution. instrumentation. InstrumentationState;
import graphql. execution. instrumentation. SimpleInstrumentation;
import graphql. execution. instrumentation. parameters
.InstrumentationExecutionParameters;
import org. springframework. stereotype. Component;

import java. util. concurrent. CompletableFuture;


import java. util. concurrent. atomic. AtomicLong;

@Component
class LogTimeInstrumentation extends SimpleInstrumentation {
@Override
public InstrumentationContext< ExecutionResult> beginExecution(
InstrumentationExecutionParameters parameters,
InstrumentationState state) {
return new InstrumentationContext<>() {
AtomicLong timeStart = new AtomicLong();

@Override
@Override
public void onDispatched(
CompletableFuture< ExecutionResult> result) {
timeStart.set( System.currentTimeMillis());
}

@Override
public void onCompleted( ExecutionResult result, Throwable t)
System.out.println( "execution time: "
+ ( System.currentTimeMillis() - timeStart.get()));
}
};
}
}

Let’s walk through this instrumentation. In the beginExecution hook,


we return an implementation of
InstrumentationContext<ExecutionResult> with two methods
onDispatched and onCompleted. They are called when the execution
starts and finishes. We save the start time and then log the
difference once the execution finishes. We’ll discuss
InstrumentationContext in more detail in the next section.

Spring for GraphQL automatically detects and registers the


instrumentation. Apart from annotating this instrumentation with
@Component, no further configuration is required.

At the time of writing, the latest Spring for GraphQL 1.1 uses
GraphQL Java 19.x. In Spring for GraphQL 1.2, GraphQL Java 20.x
will be used, which adds the improved
SimplePerformantInstrumentation class. It is designed to be more
performant and reduce object allocations.

If you are using pure GraphQL Java, the instrumentation must be


manually passed into the graphql.GraphQL builder.
GraphQLSchema schema = ...;
GraphQL graphQL = GraphQL.newGraphQL( schema)
.instrumentation(new LogTimeInstrumentation())
.build();
InstrumentationContext
InstrumentationContext is the object that will be called back when a
particular step ends. InstrumentationContext is returned by step
methods in Instrumentation such as beginExecution.

In GraphQL Java, it is represented as a simple interface.


public interface InstrumentationContext< T> {

/**
* This is invoked when the instrumentation step is initially
* dispatched
*
* @param result the result of the step as a completable future
*/
void onDispatched( CompletableFuture< T> result);

/**
* This is invoked when the instrumentation step is fully comple
*
* @param result the result of the step (which may be null)
* @param t this exception will be non-null if an exception
* was thrown during the step
*/
void onCompleted( T result, Throwable t);

The use of “dispatching” and “completion” reflects the reactive way


GraphQL Java is implemented internally, based on
CompleteableFutures. A CompleteableFuture is created and then it is
completed later. Once the linked CompleteableFuture is created, the
engine calls the onDispatched method. When the linked
CompletableFuture is finished, the engine calls the onCompleted
method.

InstrumentationContextis a generic interface, and in the previous


LogTime example, we returned an InstrumentationContext for
ExecutionResult.

InstrumentationState
Let’s discuss how state is managed in instrumentation.

We usually have only one graphql.GraphQL instance, which is reused


across all requests. Instrumentations are registered for each
graphql.GraphQL instance. So how do we maintain different
instrumentation states per request? And how do we manage the
state for complex instrumentations that require coordination across
multiple hooks?

State management is achieved with the createState method that is


called once per request and returns an InstrumentationState.
InstrumentationState is an empty Java interface representing any
kind of state. This state is passed into every hook as argument, and
the state can be tracked across multiple hooks per request.

Let’s see this in action, with a more involved instrumentation.


FieldCountInstrumentation counts the number of fields executed for a
request.
package myservice. service;

import graphql. ExecutionResult;


import graphql. execution. instrumentation. InstrumentationContext;
import graphql. execution. instrumentation. InstrumentationState;
import graphql. execution. instrumentation. SimpleInstrumentation;
import graphql. execution. instrumentation. parameters
.InstrumentationCreateStateParameters;
import graphql. execution. instrumentation. parameters
.InstrumentationExecutionParameters;
import graphql. execution. instrumentation. parameters
.InstrumentationFieldParameters;
import org. springframework. stereotype. Component;

import java. util. concurrent. CompletableFuture;


i j il i i
import java. util. concurrent. atomic. AtomicInteger;

import static graphql. execution. instrumentation


.SimpleInstrumentationContext.noOp;

@Component
class FieldCountInstrumentation
extends SimpleInstrumentation {

static class FieldCountState implements InstrumentationState {


AtomicInteger counter = new AtomicInteger();
}

@Override
public InstrumentationState createState(
InstrumentationCreateStateParameters parameters) {
return new FieldCountState();
}

@Override
public InstrumentationContext< ExecutionResult> beginField(
InstrumentationFieldParameters parameters,
InstrumentationState state) {
(( FieldCountState) state).counter.incrementAndGet();
return noOp();
}

@Override
public InstrumentationContext< ExecutionResult> beginExecution(
InstrumentationExecutionParameters parameters,
InstrumentationState state) {
return new InstrumentationContext< ExecutionResult>() {
@Override
public void onDispatched(
CompletableFuture< ExecutionResult> result) {
}

@Override
public void onCompleted( ExecutionResult result, Throwable t)
System.out.println(
"finished with " +
(( FieldCountState) state).counter.get() +
" Fields called"
);
);
}
};
}
}

We declare a new class FieldCountState holding the state we are


interested in. The createState method creates a new instance per
execution. In the beginField hook, we simply increment the counter.
After the execution finishes, we log the overall field counter.

This instrumentation is automatically detected and registered by


Spring for GraphQL as it is annotated with the @Component
annotation.

ChainedInstrumentation
Spring for GraphQL automatically chains all detected
instrumentation beans. No further configuration is required.

If using pure GraphQL Java, the instrumentations must be


manually chained together via a ChainedInstrumentation. The
Instrumentation objects are called in the order they are defined in.

In GraphQL Java, multiple instrumentations are manually chained


together with ChainedInstrumentation and then this is passed to the
graphql.GraphQL builder.

List< Instrumentation> chainedList = new ArrayList<>();


chainedList.add(new FooInstrumentation());
chainedList.add(new BarInstrumentation());

ChainedInstrumentation chainedInstrumentation
= new ChainedInstrumentation( chainedList);

GraphQLSchema schema = ...;


GraphQL graphQL = GraphQL.newGraphQL( schema)
.instrumentation( chainedInstrumentation)
.build();

Built-in instrumentations
For convenience, GraphQL Java contains built-in instrumentations.

Name
DataLoaderDispatcher For DataLoader.
Instrumentation
ExecutorInstrumentation Controls on which thread calls
to DataFetchers happen on
FieldValidationInstrumentation Validates fields and their
arguments before query
execution. If errors are
returned, execution is
aborted.
MaxQueryComplexity Prevents execution of very
Instrumentation complex operations.
MaxQueryDepthInstrumentation Prevents execution of very
large operations.
TracingInstrumentation Implements the Apollo
Tracing format.

List of instrumentation hooks


You can fully customise your instrumentation to hook into steps of
GraphQL execution.

Step Description
Step Description
beginExecution Called when the overall execution
is started
beginParse Called when parsing of the
provided document string is
started
beginValidation Called when validation of the
parsed document is started
beginExecuteOperation Called when the actual operation
is being executed (meaning a
DataFetcher is invoked)
beginSubscribedFieldEvent Called when the subscription
starts (only for subscription
operations)
beginField Called for each field of the
operation
beginFieldFetch Called when the DataFetcher for a
field is called
beginFieldComplete Called when the result of a
DataFetcher is being processed
instrumentExecutionInput Allows for changing the
ExecutionInput
instrumentDocument Allows for changing the parsed
AndVariables document and/or the variables
instrumentSchema Allows for changing the
GraphQLSchema
instrumentExecutionContext Allows for changing the
ExecutionContext class that is
used by GraphQL Java internally
during execution.
Step Description
instrumentDataFetcher Allows for changing a DataFetcher
right before it is invoked
instrumentExecutionResult Allows for changing the overall
execution result

In this chapter we covered instrumentation, a mechanism to hook


into GraphQL execution. We covered how to create instrumentation
in Spring for GraphQL that injects code to observe the execution of
a query, and instrumentation that changed runtime behavior.
DataLoader
In this chapter, we will discuss DataLoader, the library used by
GraphQL Java to batch and cache requests for data. We will discuss
the common n+1 problem and how to solve it with DataLoader’s
batching feature. We will also discuss how DataLoader’s caching
feature makes data requests more efficient.

We will demonstrate how to use DataLoader in Spring for GraphQL.


Then we’ll take a closer look at how DataLoader works in GraphQL
Java.

The n+1 problem


The n+1 problem is when related entities of an object are retrieved
inefficiently, which can cause significant performance problems. It
is a common problem when implementing DataFetchers for a
schema. Note that this is a general problem occurring in other
contexts such as SQL, it is not specific to GraphQL Java nor
GraphQL.

Let’s explain the n+1 problem with a simple example, people, and
their best friends.
type Query {
people: [Person]
}

type Person {
name: String
bestFriend: Person
}

A query could look like:


query importantPeople {
people {
bestFriend {
name
}
}
}

Let’s register two DataFetchers responsible for loading people and


then their bestFriend in Spring for GraphQL.
package myservice. service;

record Person( String name, Integer bestFriendId) {


}

package myservice. service;

import org. springframework. graphql. data. method. annotation


.QueryMapping;
import org. springframework. graphql. data. method. annotation
.SchemaMapping;
import org. springframework. stereotype. Controller;

import java. util. List;

@Controller
record PersonController( PersonService personService) {

@QueryMapping
List< Person> people() {
return personService.getAllPeople();
}

@SchemaMapping
Person bestFriend( Person person) {
return personService.getPersonById( person.bestFriendId());
}
}

While this code works, it will not perform well with large lists. For
every person in the list, we invoke the DataFetcher for the best
friend. For “n” people, we now have “n+1” service calls: one for
loading the initial list of people and then one for each of the n
people to load their best friend. This is where the name “n+1
problem” comes from. This can cause significant performance
problems as large lists will require many calls to retrieve data.

Solving the n+1 problem


The solution is instead of making one service call for each person,
we load all the best friends of all the people at once. The loading of
best friends is deferred, so they can be loaded together. This would
reduce the number of service calls from n+1 to two, regardless of
the number of people.

The n+1 problem is so common that the solution is built into


GraphQL Java, and can be accessed in Spring for GraphQL with the
controller annotation @BatchMapping. The solution makes use of the
library java-dataloader, which is maintained by the GraphQL Java
team. This library is a port of the JS library DataLoader. Note that in
this book, we will call the Java library “DataLoader” for short, and
make it explicitly clear when we talk about the JS DataLoader.

Implementing this solution is a small change in Spring for


GraphQL.
package myservice. service;

import org. springframework. graphql. data. method. annotation


.BatchMapping;
import org. springframework. graphql. data. method. annotation
.QueryMapping;
import org. springframework. stereotype. Controller;
import java. util. List;
import java. util. stream. Collectors;

@Controller
record PersonController( PersonService personService) {

@QueryMapping
List< Person> people() {
return personService.getAllPeople();
}

// Modified controller method


@BatchMapping
List< Person> bestFriend( List< Person> people) {
List< Integer> ids = people
.stream()
.map( p -> p.bestFriendId())
.collect( Collectors.toList());
return personService.getPeopleById( ids);
}
}

Replace the @SchemaMapping annotation with @BatchMapping on the


bestFriend method. With @BatchMapping, by default the field name
defaults to the method name bestFriend and the type name defaults
to the simple class name of the input List element type, Person.

Change the bestFriend method argument to a list of people, and add


logic to collect IDs from the list of people. For the DataLoader to
work, the PersonService must offer a bulk retrieval method
getPeopleById.

The @BatchMapping annotated method takes a list of people, then


loads all their best friends at once. Only two service calls are made,
instead of n+1. There is quite a bit of Spring automated magic
happening here, which we will explain in greater detail in this
chapter.
DataLoader overview
DataLoader is a library used by GraphQL Java to batch and cache
data requests. Batching solves the n+1 problem, and caching makes
data requests more efficient. This library is also maintained by the
GraphQL Java team.

There are two key implementation concepts. A DataLoader instance is


conceptually a layer deferring the loading of entities, which
identified by some key. The loading is deferred until triggered by a
“dispatch”. The timing of the dispatch is managed by GraphQL Java.
Then all the data is loaded as a batch, with the logic in a user-
implemented BatchLoader. Note that the library is called
“DataLoader”, and one of the key classes is also called “DataLoader”.
To distinguish between the two, we refer to the class with the words
“class” or “instance”.

It’s interesting to note that DataLoader is not specific to GraphQL


and is not part of the GraphQL specification. The two core features,
batching and caching, can be applied generally.

To better understand how DataLoader works, we will walk through


multiple examples. As DataLoader is not specific to GraphQL, let’s
start with a simple example without any GraphQL concepts.
// Setup
UserService userService = ...;
// expected to return a CompletableFuture
BatchLoader< Integer, User> userBatchLoader = userIds ->
userService.loadUsersById( userIds);

DataLoader< Integer, User> userLoader = DataLoaderFactory


.newDataLoader( userBatchLoader);

// Usage
CompletableFuture< User> user1CF = userLoader.load( 1);
CompletableFuture< User> user2CF = userLoader.load( 2);
userLoader.dispatchAndJoin();
// Retrieve loaded users
User user1 = user1CF.get();
User user2 = user2CF.get();

In this example, we want to load users by ID (represented as an


Integer value). We want to load the users in a batch to make the
data request efficient.

We first implement the BatchLoader Java interface by calling the


loadUsersById method. This is how our batched data will be fetched
at the dispatch point. A BatchLoader must return a
CompletableFuture.

Then we create a new DataLoader instance via DataLoaderFactory,


providing the BatchLoader. Our DataLoader has argument types
Integer and User, indicating we have a mapping from an Integer
value to a User. DataLoader is based on the idea that we have a key
(in this case Integer) being mapped to an entity (User). That means
we can load an entity with a given key.

This completes the setup and then we can start using


DataLoader.load. We immediately return these two calls with a
CompletableFuture, but note that no actual loading has happened yet.

Then we want to batch load the users. This dispatch step is triggered
with DataLoader.dispatchAndJoin(). This is a manual way to tell the
DataLoader instance that it is time to commence batch loading.
Note that in later examples, the dispatch point will be managed by
GraphQL Java.

dispatchAndJoin triggers the invocation of userBatchLoader with the


two user IDs 1 and 2 as arguments. After dispatchAndJoin returns, we
have loaded all users and we can access them.

By default, DataLoader uses a simple in-memory java.util.Map as a


cache to prevent multiple entities being loaded twice. So another
userLoader.load(1) would return a User from this cache.

We created a DataLoader instance via DataLoaderFactory, which allows


for multiple options to be configured via DataLoaderOptions. For
example, we can change the cache implementation or disable
caching completely.

DataLoader and GraphQL Java


Let’s walk through a DataLoader example with GraphQL concepts.
We will show this example with pure GraphQL Java as a way to
explain DataLoader without the Spring automated magic. You don’t
need to write this code as Spring for GraphQL’s @BatchMapping
controller annotation eliminates much of this boilerplate code.

Let’s continue with our example of people and their best friends.
This is how DataLoader works with the bestFriend DataFetcher, in
pure GraphQL Java.
import graphql. ExecutionInput;
import graphql. ExecutionResult;
import graphql. GraphQL;
import graphql. schema. DataFetcher;
import graphql. schema. GraphQLSchema;
import graphql. schema. idl. RuntimeWiring;
import graphql. schema. idl. SchemaGenerator;
import graphql. schema. idl. SchemaParser;
import graphql. schema. idl. TypeDefinitionRegistry;
import graphql. schema. idl. TypeRuntimeWiring;
import myservice. service. Person;
import myservice. service. PersonService;
import org. dataloader. BatchLoader;
import org. dataloader. DataLoader;
import org. dataloader. DataLoaderFactory;
import org. dataloader. DataLoaderRegistry;

import java. util. List;


import java. util. concurrent. CompletableFuture;
import static graphql. ExecutionInput. newExecutionInput;
import static graphql. schema. idl. RuntimeWiring. newRuntimeWiring;
import static graphql. schema. idl. TypeRuntimeWiring. newTypeWiring;
import static java. util. concurrent. CompletableFuture. completedFutu

public class PureGraphQLJava {

public static void main( String[] args) throws Exception {


// Set up schema
String sdl = """
type Query {
people: [ Person]
}

type Person {
name: String
bestFriend: Person
}
""";
TypeDefinitionRegistry parsedSdl = new SchemaParser().parse( s

PersonService personService = new PersonService();


DataFetcher< List< Person>> people = ( env) -> personService
.getAllPeople();

// Set up Person BatchLoader


BatchLoader< Integer, Person> personBatchLoader = personIds ->
completedFuture( personService.getPeopleById( personIds));

// DataFetcher implementation: select correct DataLoader and use


// it to load the data
String PERSON_DATA_LOADER = "person";

DataFetcher< CompletableFuture< Person>> bestFriendDF = ( env) ->


Person person = env.getSource();
DataLoader< Integer, Person> dataLoader
= env.getDataLoader( PERSON_DATA_LOADER);
return dataLoader.load( person.bestFriendId());
};

// Make executable schema


TypeRuntimeWiring queryWiring = newTypeWiring( "Query")
.dataFetcher( "people", people)
.build();
();
TypeRuntimeWiring bestie = newTypeWiring( "Person")
.dataFetcher( "bestFriend", bestFriendDF)
.build();
RuntimeWiring runtimeWiring = newRuntimeWiring()
.type( queryWiring)
.type( bestie)
.build();
= new SchemaGenerator()
GraphQLSchema schema
.makeExecutableSchema( parsedSdl, runtimeWiring);

// Per request:

// Creating the DataLoader


DataLoader< Integer, Person> personDataLoader = DataLoaderFactory
.newDataLoader( personBatchLoader);

// Adding DataLoader instance to a DataLoaderRegistry


DataLoaderRegistry registry = new DataLoaderRegistry();
registry.register( PERSON_DATA_LOADER, personDataLoader);

String query = """


query everyone {
people {
name
bestFriend {
name
}
}
} """;

// Providing DataLoader instance to the ExecutionInput


ExecutionInput executionInput = newExecutionInput()
.query( query)
.dataLoaderRegistry( registry)
.build();

// Execute query
GraphQL graphQL = GraphQL.newGraphQL( schema).build();
ExecutionResult executionResult = graphQL.execute( executionInpu
}
}
The Person DataLoader is registered with the bestFriend
DataFetcher.

Note that a new DataLoader instance needs to be created per request,


as it holds state about what has been dispatched and cached, and
this should not be shared across requests.

As there are often multiple entities we want to use with DataLoader,


there can be multiple different DataLoader instances per request.
These are managed by DataLoaderRegistry, which identifies each
DataLoader instance by a name. It is also possible to dispatch all
registered DataLoader instances at once with
DataLoaderRegistry.dispatchAll.

We then create an instance of ExecutionInput, which represents the


incoming GraphQL request. See the Request chapter for more on
ExecutionInput. The request is then executed and returns the result.

It is important to highlight that there is no “dispatch” in this pure


GraphQL Java code. This is because GraphQL Java knows when to
dispatch with the help of the DataLoaderDispatcherInstrumentation,
which is automatically added to the GraphQL instance.

DataLoader and Spring for GraphQL


Now that we’ve seen the pure GraphQL Java way to use DataLoader,
we’ll walk through how to implement the same logic in Spring for
GraphQL. We’ll begin with an example that does not use the
@BatchMapping controller annotation to demonstrate how it works.

In Spring for GraphQL, a single, central BatchLoaderRegistry exposes


factory methods and a builder to create and register BatchLoaders.
The Spring Boot starter declares a BatchLoaderRegistry bean, which
can be injected into a component such as a controller in the
example below. Spring for GraphQL injects the DataLoader instance
into the DataFetcher.
package myservice. service;

import org. dataloader. DataLoader;


import org. springframework. graphql. data. method. annotation
.QueryMapping;
import org. springframework. graphql. data. method. annotation
.SchemaMapping;
import org. springframework. graphql. execution
.BatchLoaderRegistry;
import org. springframework. stereotype. Controller;
import reactor. core. publisher. Flux;

import java. util. List;


import java. util. concurrent. CompletableFuture;

@Controller
class PersonController {

PersonService personService;

PersonController(
PersonService personService,
BatchLoaderRegistry batchLoaderRegistry) {
this.personService = personService;

// Registering the BatchLoader


batchLoaderRegistry
.forTypePair( Integer.class, Person.class)
.registerBatchLoader(
( integers, batchLoaderEnvironment) -> Flux
.fromIterable( personService.getPeopleById( integers)
)
);
}

@QueryMapping
List< Person> people() {
return personService.getAllPeople();
}
// Manually using DataLoader instance in DataFetcher
@SchemaMapping
CompletableFuture< Person> bestFriend(
Person person, DataLoader< Integer, Person> dataLoader) {
// Using the DataLoader
return dataLoader.load( person.bestFriendId());
}

BatchLoaderRegistry is a map of names to BatchLoaders. Spring for


GraphQL automatically chooses the name to be the full class name
of the entity we want to load. This name can be customized. Note
that the BatchLoaderRegistry expects BatchLoaders to return a Flux and
not a CompletableFuture.

Based on the automatically chosen BatchLoader name, we can declare


DataLoader<Integer, Person> dataLoader as a method argument and
Spring knows which DataLoader to inject based on the types
<Integer, Person>.

In our example, the bestFriend DataFetcher only delegates directly


to the DataLoader. This allows us to further reduce our code by
using the @BatchMapping annotation, as we saw earlier in this chapter.
package myservice. service;

import org. springframework. graphql. data. method. annotation


.BatchMapping;
import org. springframework. graphql. data. method. annotation
.QueryMapping;
import org. springframework. stereotype. Controller;

import java. util. List;


import java. util. stream. Collectors;

@Controller
class PersonController {

PersonService personService;
// This constructor is written to emphasize
// Spring for GraphQL automation.
// You can instead use a record class
// and use the generated constructor.
PersonController( PersonService personService) {
this.personService = personService;
// No longer require BatchLoaderRegistry
// nor manual BatchLoader registration
}

@QueryMapping
List< Person> people() {
return personService.getAllPeople();
}

// Using @BatchMapping instead of @SchemaMapping


@BatchMapping
List< Person> bestFriend( List< Person> people) {
List< Integer> ids = people
.stream()
.map( p -> p.bestFriendId())
.collect( Collectors.toList());
return personService.getPeopleById( ids);
}

The @BatchMapping annotation helps us remove much of the


boilerplate code.

It is important to note that Person in this final example is the key


type for the DataLoader signature rather than Integer. The signature
is now DataLoader<Person, Person> rather than DataLoader<Integer,
Person>. Therefore, it is critical that Person implements equals and
hashcode methods in order to work as a key.

Spring for GraphQL automatically registers a BatchLoader with the


type pair Person, Person in the BatchLoaderRegistry, so we no longer
need to manually do this step. Note that Person is both the key and
value type in this automatically registered BatchLoader. The
BatchLoader logic is moved to the last line of the bestFriend method.

Then Spring for GraphQL creates a DataLoader instance with the


full class name of Person. In our bestFriend method, a list of people
is provided as an argument. We then extract the best friend IDs and
delegate to the DataLoader instance, which is analogous to
dataLoader.load(person.bestFriendId) in the previous example.

@BatchMapping method signature


Batch mapping methods support the following arguments in Spring
for GraphQL:

Argument Description
List<T> The list of source objects
java.security.Principal Spring Security principal
@ContextValue(name = A specific value from the
“foo”) GraphQLContext
GraphQLContext The entire GraphQLContext
BatchLoaderEnvironment org.dataloader.BatchLoaderWithContext
from DataLoader itself

The supported return types are:

Return Type Description


Mono<Map<K, V>> The mapping from key to value
Flux<V> A reactive sequence of resolved values
Map<K, V> Non-reactive map of key to value
Collection<V> Non-reactive collection (including list)
Return Type Description
Callable<Map<K,V>>, Imperative variant to be invoked
Callable<Collection<V>> asynchronously

In this chapter, we covered the n+1 problem when too many service
calls are used to fetch data. We solved the problem with
DataLoader, which is conveniently made available with
@BatchMapping in Spring for GraphQL. We then had a closer look at
how DataLoader works under the hood.
Testing
Spring for GraphQL provides helpers for GraphQL testing in a
dedicated artifact org.springframework.graphql:spring-graphql-test.
Testing a GraphQL service can happen on multiple levels, with
different scopes. In this chapter, we will discuss how Spring for
GraphQL makes it easier to write tests. At the end of the chapter,
we’ll conclude with our recommendations for writing good tests.

In this chapter, we will make use of standard testing libraries. We


will use JUnit 5, Mockito, AssertJ, and the standard Spring Boot
testing capabilities.

Unit testing DataFetcher


DataFetchers are the central concept for implementing a Spring for
GraphQL service, because they are the link between your schema
and the data. The main guideline for writing DataFetchers is that
they should be very thin and delegate the actual work to a layer of
code below it. The only purpose of a DataFetcher should be to take
care of GraphQL-specific aspects. Some examples are reading and
validation input, mapping return data, handling exceptions, and
converting it to an error.

By purposefully designing DataFetchers to be thin, they also


become easier to write unit tests for.
In the first DataFetchers chapter, we discussed that DataFetchers in
Spring for GraphQL are registered via controller methods with a
@SchemaMapping annotation, or one of the shortcut annotations such
as @QueryMapping.

Initially, we’ll explore testing with a simple Hello World example, to


illustrate the different types of testing with Spring for GraphQL.
Later in this chapter, we’ll also write tests for a larger Pet schema.

Here is the simple schema for our Hello World example:


type Query {
hello: String
}

A simple query:
query greeting {
hello
}

And a simple DataFetcher registered with the @QueryMapping


controller annotation.
package myservice. service;

import org. springframework. graphql. data. method. annotation


.QueryMapping;
import org. springframework. stereotype. Controller;

@Controller
class GreetingController {

@QueryMapping
String hello() {
return "Hello, world!";
}
}

The hello DataFetcher is a good example of a thin DataFetcher that


can be easily unit tested.
package myservice. service;

import org. junit. jupiter. api. Test;


import org. junit. jupiter. api. extension. ExtendWith;
import org. mockito. junit. jupiter. MockitoExtension;

import static org. assertj. core. api. Assertions. assertThat;

@ExtendWith( MockitoExtension.class)
class GreetingControllerTest {

@Test
void testHelloDataFetcher() {
GreetingController greetingController = new GreetingController

String greeting = greetingController.hello();


// Verify returned object is expected one
assertThat( greeting).isEqualTo( "Hello, world!");
}
}

If it becomes difficult to unit test a DataFetcher, we should try to


break it apart and write multiple smaller unit tests.

As we saw in the DataFetchers chapter, a DataFetcher’s get method


accepts an input DataFetchingEnvironment, which is a Java interface
containing the necessary GraphQL information to fetch data,
including source, schema, document, context, selection set, and
much more. We also saw in the DataFetchers chapter that Spring
for GraphQL provides shortcuts to particular fields in the
DataFetchingEnvironment, to be used as inputs into DataFetchers
registered via controller annotations. When writing DataFetcher
tests, we recommend mocking the DataFetchingEnvironment and only
implement the parts needed.

GraphQlTester
GraphQlTester is the primary class to help us test in Spring for
GraphQL. GraphQlTester is a Java interface with a few inner
interfaces, which provides a rich API to execute requests and verify
responses. There are a number of implementations for different
types of tests:

HttpGraphQlTester for HTTP-based testing, leveraging a


WebTestClient
WebGraphQlTester for testing a WebGraphQlHandler
ExecutionGraphQlServiceTester for testing a
ExecutionGraphQlService
WebSocketGraphQlTester for testing WebSocket requests

These different classes are normally only used to create a


GraphQlTester instance. For use in code, we only reference
GraphQlTester.

For example, here is a query for our Hello World example from
earlier in this chapter:
query greeting {
hello
}

With GraphQlTester, we can write a test to verify this query is


executed, and returns the expected response “Hello, world!”. This
example demonstrates the HttpGraphQlTester.
package myservice. service;

import org. junit. jupiter. api. Test;


import org. springframework. beans. factory. annotation. Autowired;
import org. springframework. boot. test. context. SpringBootTest;
import org. springframework. graphql. test. tester. HttpGraphQlTester;

@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class GreetingControllerTest {
@Autowired
HttpGraphQlTester graphQlTester;

@Test
void usingTester() {
graphQlTester
.document( "query greeting { hello }")
.execute()
.path( "hello")
.entity( String.class)
.isEqualTo( "Hello, world!");
}
}

We’ll soon explain all the parts in this test, but let’s start by
focusing on the GraphQlTester. We provide a document with document,
execute it, select a specific part of the response to verify with path,
and finally verify it is the string “Hello, world!”.

To make testing code more compact, note that the document in this
example is provided on a single line. This is equivalent to a query
with new lines, because new lines and additional whitespace are
ignored in GraphQL syntax.

We’ll see how this GraphQlTester fits into a test class in multiple
examples later in this chapter.

Let’s walk through each of these method calls in this test.

document or documentName
A document is provided with document.

Alternatively, we could use GraphQlTester.documentName to specify a


resource .graphql file containing a document, instead of using an
inline string. By default, GraphQlTester expects the file to be in
src/test/resources/graphql-test.
For example, you could save this query in a file called
greeting.graphql in the directory src/test/resources/graphql-test.

# Save this file as "greeting.graphql" in src/test/resources/graphql


query greeting {
hello
}

Then we could rewrite our earlier test with documentName to use this
resource file containing the document:
graphQlTester
.documentName( "greeting")
.execute()
.path( "hello")
.entity( String.class)
.isEqualTo( "Hello, world!");

GraphQlTester.Request and execute


GraphQlTester.document returns a GraphQlTester.Request.

GraphQlTester.Request specifies the details of a request (operation


name, variables) and then GraphQlTester.Request.execute returns a
GraphQlTester.Response.

GraphQlTester.Response , path , entity ,


entityList
GraphQlTester.Response extends Traversable, which is a simple
interface:
interface Traversable {
Path path( String path);
}
We can use any JsonPath with path. In our Hello World example, we
used the path "hello". In the more complex Pets example later in
this chapter, we’ll see how to select names from a list of Pets.

You can traverse a GraphQL response by providing a path and


returning a GraphQlTester.Path, which is again itself a Traversable. A
Path has different ways of asserting or converting the current part of
the GraphQL response. We can convert it to an entity or entityList
and then assert further.

For example, a Pet can be converted to an entity and then asserted


further. For example, a very basic favorite Pet schema:
type Query {
favoritePet: Pet
}

type Pet {
name: String
}

With a small Pet class:


package myservice. service;

record Pet( String name) {


}

And a basic DataFetcher that queries a database:


package myservice. service;

import org. springframework. graphql. data. method. annotation


.QueryMapping;
import org. springframework. stereotype. Controller;

@Controller
class PetsController {

@QueryMapping
Pet favoritePet() {
// return favorite pet from database
}

To convert the current part of the GraphQL response into a Pet


entity in GraphQlTester, use entity(Pet.class), then test your
assertion afterwards:
Pet favoritePet = graphQlTester
.document( "query whoIsAGoodBoyOrGirl { favoritePet { name }
.execute()
.path( "favoritePet")
.entity( Pet.class)
.get()
// Your assertion here

See the more complicated Pets example later in this chapter for
usage of entityList when a list of Pets is returned.

errors
By default, a GraphQL error in the response will not cause the test
to fail since a partial response in GraphQL is still a valid answer. See
why partial responses and nullable fields are valuable in the Schema
Design chapter.

However, in a test, you usually want to check that no errors were


returned. To verify that no errors are returned in a test, add
.errors().verify().

graphQlTester
.document( "query greeting { hello }")
.execute()
.errors()
.verify() // Ensure there are no GraphQL errors
.path( "hello")
.entity( String.class)
.isEqualTo( "Hello, world!");

To filter out particular errors, we can use errors().filter(error ->


...).verify() to exclude errors from the test. To verify that an error
is present, we can use .errors().expect(error -> ...).verify().

Testing different layers


As explained in the Spring for GraphQL overview at the beginning of
the book, there are three key Spring for GraphQL classes:
GraphQlHttpHandler, WebGraphQlHandler, and ExecutionGraphQlService.
We can also test the HTTP transport layer.

To recap, a request passes through three primary classes in Spring


for GraphQL, each with a distinct responsibility, as shown in the
diagram “Spring for GraphQL classes”:

1. A general purpose HTTP request invokes GraphQlHttpHandler


converts the request into a WebGraphQlRequest.
2. WebGraphQlHandler takes the WebGraphQlRequest, and calls
ExecutionGraphQlService to execute the request.
3. ExecutionGraphQlService ultimately invokes GraphQL Java.

Spring for GraphQL classes

We can test these different layers:


End-to-end over HTTP: creating an entirely separate process and
test via HTTP
Application test: test in the same process without the full HTTP
layer, includes a client
Test on server side (without a client) starting from
WebGraphQLHandler
Test on server side (without a client) starting from
ExecutionGraphQlService

Note that the next few sections focus on HTTP where tests include
transport. For WebSocket tests, see subscriptions testing section
later in this chapter.

End-to-end over HTTP


Spring Boot allows us to start a whole service as a separate process
and test it end-to-end over HTTP by using the SpringBootTest
annotation.
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

As mentioned earlier in this chapter, we have a HttpGraphQlTester to


test a GraphQL API via HTTP. It builds on top of WebTestClient.

By combining the @SpringBootTest annotation and the


HttpGraphQlTester, we can write an end-to-end test:

package myservice. service;

import org. junit. jupiter. api. Test;


import org. springframework. beans. factory. annotation. Autowired;
import org. springframework. boot. test. context. SpringBootTest;
import org. springframework. graphql. test. tester. HttpGraphQlTester;

@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class E2ETest {

@Autowired
HttpGraphQlTester graphQlTester;

@Test
void testHello() {
String document = "query greeting { hello }";
graphQlTester.document( document)
.execute()
.path( "hello")
.entity( String.class)
.isEqualTo( "Hello, world!");
}
}

This tests a whole GraphQL service over HTTP, verifying that the
request query greeting { hello } returns “Hello, world!”.

Application test
To test the whole service, without the HTTP transport layer, we can
start the whole application in the same Java Virtual Machine (JVM).

To automatically configure a HttpGraphQlTester, use the


@AutoConfigureHttpGraphQlTester annotation.

package myservice. service;

import org. junit. jupiter. api. Test;


import org. springframework. beans. factory. annotation. Autowired;
import org. springframework. boot. test. autoconfigure. graphql. tester
.AutoConfigureHttpGraphQlTester;
import org. springframework. boot. test. context. SpringBootTest;
import org. springframework. graphql. test. tester. HttpGraphQlTester;

@SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.MOC


@AutoConfigureHttpGraphQlTester
class MockedTest {
@Autowired
HttpGraphQlTester graphQlTester;

@Test
void testHello() {
String document = "query greeting { hello }";
graphQlTester.document( document)
.execute()
.path( "hello")
.entity( String.class)
.isEqualTo( "Hello, world!");
}

This test only verifies the request inside the application, inside the
JVM. It is different to the previous end-to-end test, as the request in
this test does not go through the HTTP transport layer.

WebGraphQlHandler test
A WebGraphQlHandler test enables direct testing of WebGraphQlHandler.
This includes WebGraphQlInterceptor, because the WebGraphQlHandler
manages interceptors. Create a new tester instance by providing the
relevant WebGraphQlHandler.
package myservice. service;

import org. junit. jupiter. api. Test;


import org. springframework. beans. factory. annotation. Autowired;
import org. springframework. boot. test. context. SpringBootTest;
import org. springframework. graphql. server. WebGraphQlHandler;
import org. springframework. graphql. test. tester. WebGraphQlTester;

@SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.MOC


class WebGraphQlTest {

@Autowired
WebGraphQlHandler webGraphQlHandler;
p Q p Q ;

@Test
void testHello() {
WebGraphQlTester webGraphQlTester
= WebGraphQlTester.create( webGraphQlHandler);
String document = "query greeting { hello }";
webGraphQlTester.document( document)
.execute()
.path( "hello")
.entity( String.class)
.isEqualTo( "Hello, world!");
}
}

ExecutionGraphQlService test
ExecutionGraphQlServiceTester enables direct testing of
ExecutionGraphQlService. Create a new tester instance by providing
the relevant ExecutionGraphQlService.
package myservice. service;

import org. junit. jupiter. api. Test;


import org. springframework. beans. factory. annotation. Autowired;
import org. springframework. boot. test. context. SpringBootTest;
import org. springframework. graphql. ExecutionGraphQlService;
import org. springframework. graphql. test. tester
.ExecutionGraphQlServiceTester;

@SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.MOC


class GraphQlServiceTest {

@Autowired
ExecutionGraphQlService graphQlService;

@Test
void testHello() {
ExecutionGraphQlServiceTester graphQlServiceTester
= ExecutionGraphQlServiceTester.create( graphQlService);
String document = "query greeting { hello }";
g q y g g { } ;
graphQlServiceTester.document( document)
.execute()
.path( "hello")
.entity( String.class)
.isEqualTo( "Hello, world!");
}

Focused GraphQL testing with


@GraphQlTest
For a more minimal testing setup, we can use the @GraphQlTest
annotation instead of @SpringBootTest. @GraphQlTest configures a slice
test, which will load only a subset of an application, focusing only
on the GraphQL layer. It is a ExecutionGraphQlServiceTester, with the
added feature of only automatically loading what is strictly needed
to execute the request, and nothing else.

Let’s examine @GraphQlTest with a Pet service. This is the schema:


type Query {
pets: [Pet]
}

type Pet {
name: String
}

The query we want to test is:


query myPets {
pets {
name
}
}
This is the Pet controller, which includes a static Pet class and the
pets DataFetcher annotated with @QueryMapping:

package myservice. service;

import org. springframework. graphql. data. method. annotation


.QueryMapping;
import org. springframework. stereotype. Controller;

import java. util. List;

@Controller
record PetsController( PetService petService) {

@QueryMapping
List< Pet> pets() {
return petService.getPets();
}

The controller uses a Pet service, which fetches a list of Pets from a
data source, which could be a database or another service, or
anything else.
package myservice. service;

import org. springframework. stereotype. Service;

@Service
class PetService {

List< Pet> getPets() {


// Fetch data from database, or elsewhere
}

In this example, our pets DataFetcher is very thin, it only delegates


to PetService. This is quite realistic, and usually we aim for this kind
of design.
Now with @GraphQlTest, our test setup includes the PetController, but
not the PetService because it doesn’t belong to the GraphQL layer
itself.

This means we need to create a mock for the PetService.


package myservice. service;

import org. junit. jupiter. api. Test;


import org. mockito. Mockito;
import org. springframework. beans. factory. annotation. Autowired;
import org. springframework. boot. test. autoconfigure. graphql
.GraphQlTest;
import org. springframework. boot. test. mock. mockito. MockBean;
import org. springframework. graphql. test. tester. GraphQlTester;

import java. util. List;

@GraphQlTest( PetsController.class)
class PetsControllerTest {

@Autowired
GraphQlTester graphQlTester;

@MockBean
PetService petService;

@Test
void testPets() {
Mockito.when( petService.getPets())
.thenReturn( List.of(
new Pet( "Luna"),
new Pet( "Skipper")
));

graphQlTester
.document( "query myPets { pets { name } }")
.execute()
.path( "pets[*].name")
.entityList( String.class)
.isEqualTo( List.of( "Luna", "Skipper"));
}
}

This is a suitable setup, especially if we have a complicated setup


below the controller. Mocking the Pet service dependency allows for
a very focused and lean test.

As an alternative, you could verify there were at least two pet names
by replacing the last block of the test above with:
graphQlTester
.document( "query myPets { pets { name } }")
.execute()
.path( "pets[*].name")
.entityList( String.class)
.hasSizeGreaterThan( 2);

In these examples, the path for Pets was more complex than our
Hello World example. "pets[*].name" means select all names of all
pets. We can use any JsonPath with path.

Subscription testing
GraphQlTester offers an executeSubscription method that returns a
GraphQlTester.Subscription. This can be then further converted to a
Flux and verified. To test Flux more easily, add the Reactor testing
library io.projectreactor:reactor-test.

Testing our hello subscription from the Subscription chapter end to


end looks like this.
# Every schema needs a Query type
type Query {
notUsed: String
}

type Subscription {
hello: String
}

package myservice. service;

import org. springframework. graphql. data. method. annotation


.SubscriptionMapping;
import org. springframework. stereotype. Controller;
import reactor. core. publisher. Flux;

import java. time. Duration;


import java. util. List;

@Controller
class HelloController {

@SubscriptionMapping
Flux< String> hello() {
Flux< Integer> interval = Flux.fromIterable( List.of( 0, 1, 2))
.delayElements( Duration.ofSeconds( 1));
return interval.map( integer -> "Hello " + integer);
}
}

If you haven’t already, set the WebSocket path in


application.properties.

spring.graphql.websocket.path=/graphql

package myservice. service;

import org. junit. jupiter. api. BeforeEach;


import org. junit. jupiter. api. Test;
import org. springframework. beans. factory. annotation. Value;
import org. springframework. boot. test. context. SpringBootTest;
import org. springframework. graphql. test. tester. GraphQlTester;
import org. springframework. graphql. test. tester
.WebSocketGraphQlTester;
import org. springframework. web. reactive. socket. client
.ReactorNettyWebSocketClient;
import reactor. core. publisher. Flux;
import reactor. test. StepVerifier;
import java. net. URI;

@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class SubscriptionTest {

@Value( "http://localhost:${local.server.port}"
+ "${spring.graphql.websocket.path}")
private String baseUrl;

GraphQlTester graphQlTester;

@BeforeEach
void setUp() {
URI url = URI.create( baseUrl);
this.graphQlTester = WebSocketGraphQlTester.builder(
url, new ReactorNettyWebSocketClient()
).build();
}

@Test
void helloSubscription() {
Flux< String> hello = graphQlTester
.document( "subscription mySubscription {hello}")
.executeSubscription()
.toFlux( "hello", String.class);

StepVerifier.create( hello)
.expectNext( "Hello 0")
.expectNext( "Hello 1")
.expectNext( "Hello 2")
.verifyComplete();
}
}

We create a WebSocketGraphQlTester by proving the URL and a


WebSocket client. The default WebFlux server is Netty, meaning we
use the ReactorNettyWebSocketClient.

In the actual test, we execute the subscription request and convert it


to a Flux<String> by selecting the hello values from each response.
Then we use the StepVerifier from the Reactor testing library to
verify that we get exactly the three events we expect.

The same way we have tested subscription end to end here also
allows us to test subscriptions on different layers.

Testing recommendations
A general guide for writing good tests is to have the smallest or
most focused test possible that verifies what we want to test.

Here are some examples:

If we want to test our DataFetcher calling the PetService, a unit


test is the right approach.
If we want to make sure our GraphQL setup is correct
(e.g. verify that a DataFetcher is mapped to the correct field),
@GraphQlTest is the right approach.
If we want to test a WebGraphQlInterceptor, then a
WebGraphQlHandler test is most often good enough.

It is good to have some basic end-to-end Spring Boot tests, ensuring


that the whole service starts and receives requests as expected.
However, keep in mind that these end-to-end tests are mostly
focused on setup. If you want to focus on verifying behavior, it is
better to use a mock environment.

Testing all layers together involves running a SpringBootTest in a


mock environment with a HttpGraphQlHandler. This is useful to test
security which we will discuss in the next chapter on Security.

These testing guidelines fit into the Test Pyramid model. The idea is
to have more of focused, smaller and faster tests, compared to the
number of tests that run longer, test more aspects, and are harder to
debug. This model gives us some guidance about the amount of
tests per test type. It is preferable to have more unit tests than
WebGraphQlHandlerTests, and it is preferable to have more
WebGraphQlHandlerTests than the number of end-to-end tests.

In this chapter, we covered unit testing, application testing, and


end-to-end testing with Spring for GraphQL. We also discussed
testing best practices and how to think about testing your Spring for
GraphQL service.
Security
Spring for GraphQL makes securing GraphQL much simpler by
integrating familiar concepts from Spring Security.

In this chapter, auth is short for both authentication and


authorization.

Although securing GraphQL services is important, note that the


GraphQL specification does not prescribe any specific auth logic,
because it does not dictate where the data comes from. As a result of
this, GraphQL Java does not provide auth support. Another key
reason for omitting auth support in GraphQL Java was the
impracticality of a generic engine attempting to support auth with is
tightly coupled with transport layer concerns.

As security is not part of the GraphQL Java engine, this chapter will
instead focus on using Spring for GraphQL and Spring Security to
secure your GraphQL service. Spring for GraphQL has built-in,
dedicated support for Spring Security.

Securing a Spring for GraphQL service


We’ll walk through key concepts with an example service for
managing store orders. We intend for the important parts to be
realistic to ensure we have a working and secured service, while we
will take some shortcuts to keep this chapter short.
A web client will use our online store with a GraphQL API via HTTP,
where they can query all the orders for the current user. For brevity,
only admins can remove an order, and we will not cover order
creation.

Here is our store orders schema.


type Query {
# Every logged-in user can query orders
myOrders: [Order]
}

type Order {
id: ID
details: String
}

type Mutation {
# Only Admins can delete orders
deleteOrder(input : DeleteOrderInput!): DeleteOrderPayload
}

input DeleteOrderInput {
orderId: ID
}

type DeleteOrderPayload {
success: Boolean
}

Let’s implement a very simple Java class OrderService that loads and
changes orders, which are stored in memory.
package myservice. service;

record Order( String id, String details, String owner) {


}

package myservice. service;

import org. springframework. stereotype. Service;


import java. util. ArrayList;
import java. util. List;
import java. util. stream. Collectors;

@Service
class OrderService {

private final List< Order> orders;

OrderService() {
// A mutable list of orders
this.orders = new ArrayList<>( List.of(
new Order( "1", "Kibbles", "Luna"),
new Order( "2", "Chicken", "Skipper"),
new Order( "3", "Rice", "Luna"),
new Order( "4", "Lamb", "Skipper"),
new Order( "5", "Bone", "Luna"),
new Order( "6", "Toys", "Luna"),
new Order( "7", "Toys", "Skipper")
));
}

List< Order> getOrdersByOwner( String owner) {


return orders
.stream()
.filter( order -> order.owner().equals( owner))
.collect( Collectors.toList());
}

boolean deleteOrder( String orderId) {


return orders.removeIf( order -> order.id().equals( orderId));
}

This simple Java class includes the basic functional aspects we


want: getOrdersByOwner returns the list of orders for the provided
owner and deleteOrder deletes an order.

Our authentication will use session cookies and we require a valid


session for every request. This means that before the execution of a
request starts, we need to ensure that we have a valid session,
otherwise we return an HTTP 401 status code.

Once we have a valid session identifying the user, we can query


their orders and check if they can delete an order. This
authorization part is handled during GraphQL execution on the
DataFetcher-level.

To add Spring Security to our project, add the dependency

org.springframework.boot:spring-boot-starter-security

and then create the following Config class.

package myservice. service;

import org. springframework. context. annotation. Bean;


import org. springframework. context. annotation. Configuration;
import org. springframework. security. config. annotation. web
.reactive.EnableWebFluxSecurity;
import org. springframework. security. config. web. server
.ServerHttpSecurity;
import org. springframework. security. core. userdetails
.MapReactiveUserDetailsService;
import org. springframework. security. core. userdetails. User;
import org. springframework. security. core. userdetails
.UserDetails;
import org. springframework. security. web. server
.SecurityWebFilterChain;
import org. springframework. security. web. server. authentication
.RedirectServerAuthenticationSuccessHandler;

@Configuration
@EnableWebFluxSecurity
class Config {

@Bean
SecurityWebFilterChain springWebFilterChain( ServerHttpSecurity h
throws Exception {
http.formLogin().authenticationSuccessHandler(
new RedirectServerAuthenticationSuccessHandler( "/graphiql
);

return http
.csrf( ServerHttpSecurity.CsrfSpec:: disable)
.authorizeExchange( exchanges -> {
exchanges.anyExchange().authenticated();
})
.build();
}

@Bean
@SuppressWarnings( "deprecation")
MapReactiveUserDetailsService userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncod
UserDetails luna = userBuilder
.username( "Luna").password( "password").roles( "USER")
.build();
UserDetails andi = userBuilder
.username( "Andi").password( "password").roles( "USER", "ADMIN
.build();
return new MapReactiveUserDetailsService( luna, andi);
}
}

Let’s step through this security configuration:

It activates Spring Security for reactive WebFlux applications


with the annotation @EnableWebFluxSecurity.
It configures a simple form-based login, which should redirect
to /graphiql if successful. GraphiQL is an interactive playground
which is included in Spring for GraphQL.
It disables CSRF protection to allow us to use GraphiQL for easy
testing. We do not recommend disabling CSRF for a real service.
It requires that every request is authenticated.
It configures the list of users, simply by declaring them and
including their password directly in our configuration. Of
course, hard coded users is only acceptable for demo code.
The simple form-based login allows us to login by visiting /login in
a browser and to logout via /logout. If not logged in, we are get
redirected to /login. This is a very convenient way for us to switch
users easily and verify the expected behavior manually.

Let’s create the last required class, the OrderController that


implements our DataFetcher.

To implement the myOrders method, we need to know the current


logged-in user. Spring for GraphQL provides access to the current
java.security.Principal by simply declaring it as a Java argument.
Spring for GraphQL made this possible by integrating with Spring
Security.
package myservice. service;

import org. springframework. graphql. data. method. annotation


.QueryMapping;
import org. springframework. stereotype. Controller;

import java. security. Principal;


import java. util. List;

@Controller
record OrderController( OrderService orderService) {

@QueryMapping
List< Order> myOrders( Principal principal) {
return orderService.getOrdersByOwner( principal.getName());
}

This is the first part of implementing authorization. We use the


current user to filter the list of orders returned. We only return the
orders belonging to the right user.

The second DataFetcher is concerned with deleting an order. While


we already made sure that we have valid authentication, we must
also check authorization. Only admins can delete orders. Therefore,
we need to verify the role of the current user before we delete any
orders.
package myservice. service;

record DeleteOrderInput( String orderId) {


}

package myservice. service;

record DeleteOrderPayload( boolean success) {


}

package myservice. service;

import org. springframework. graphql. data. method. annotation

.Argument;
import org. springframework. graphql. data. method. annotation
.MutationMapping;
import org. springframework. graphql. data. method. annotation
.QueryMapping;
import org. springframework. security. access
.AccessDeniedException;
import org. springframework. security. authentication
.UsernamePasswordAuthenticationToken;
import org. springframework. security. core. authority
.SimpleGrantedAuthority;
import org. springframework. stereotype. Controller;

import java. security. Principal;


import java. util. List;

@Controller
record OrderController( OrderService orderService) {

@QueryMapping
List< Order> myOrders( Principal principal) {
return orderService.getOrdersByOwner( principal.getName());
}

@MutationMapping
DeleteOrderPayload deleteOrder( @Argument DeleteOrderInput input,
Principal principal) {
UsernamePasswordAuthenticationToken user
= ( UsernamePasswordAuthenticationToken) principal;
if (! user.getAuthorities()
.contains(new SimpleGrantedAuthority( "ROLE_ADMIN"))) {
throw new AccessDeniedException( "Only admins can delete ord
}
return new DeleteOrderPayload( orderService
.deleteOrder( input.orderId()));
}
}

We use the injected Principal again, but this time we use it to verify
that the current user has the correct role, rather than filtering
orders. If the user is unauthorized, we throw an
AccessDeniedException.

We can test this service manually by logging in as “Luna” at /login


with the password “password” and querying all orders via the
GraphiQL playground, as shown in the screenshot “Luna’s orders”.
query lunaOrders {
myOrders {
id
}
}
Luna’s orders

If we try to delete an order as Luna, we will get a GraphQL error


telling us we are unauthorized, as shown in the screenshot “Luna is
unauthorized to delete orders”.
mutation lunaUnauthorized {
deleteOrder(input : {orderId: 1}) {
success
}
}
Luna is unauthorized to delete orders

After we log out via /logout and login as “Andi” (with password
“password”), we can delete an order.

It’s important to note that an unauthorized attempt to delete an


order with a logged-in user, will not cause an HTTP error. The
status code of the HTTP response is 200 containing a GraphQL error.

Spring for GraphQL support for security


In the store orders example, we saw one feature of Spring for
GraphQL supporting Spring Security: we can declare a
java.security.Principal as input for annotated methods (this
includes @BatchMapping).

Spring for GraphQL also automatically registers a


ReactiveSecurityDataFetcherExceptionResolver (or
SecurityDataFetcherExceptionResolver for WebMVC) handling
AuthenticationException and AccessDeniedException.
AuthenticationException and AccessDeniedException result in a
GraphQL error with error type UNAUTHORIZED and FORBIDDEN
respectively.

Method security
One problem with our store order example above is the location
where we perform the authorization checks. They happen directly
inside each DataFetcher. This is not great. The better and
recommended way is to secure the OrderService itself, so that it is
secure, regardless which DataFetcher uses it.

Spring Security offers method-level annotations to perform


authorization in a declarative way. As Spring for GraphQL integrates
Spring Security, we can use these annotations to refactor
OrderService.

@PreAuthorize( "hasRole('ADMIN')")
Mono< Boolean> deleteOrder( String orderId) {

return Mono.just( orders.removeIf( order -> order.id().equals( or


}

In order for PreAuthorize to work in a WebFlux service, the


annotated method must return Mono or Flux. Therefore, we need to
wrap our return value in a Mono.

We also refactor getOrdersByOwner into getOrdersForCurrentUser and


we are using ReactiveSecurityContextHolder to get the current logged-
in user.
Mono< List< Order>> getOrdersForCurrentUser() {
return ReactiveSecurityContextHolder.getContext()
.map( securityContext -> {
Principal principal = securityContext.getAuthentication();
return orders
.stream()
.filter( order -> order.owner().equals( principal.getName(
.collect( Collectors.toList());
});
}

We need to change the return type to Mono because


ReactiveSecurityContextHolder itself is reactive. Finally, we need to
add @EnableReactiveMethodSecurity to the Config class to enable these
annotations.

Putting it all together, here is the full source code for Config,
Controller, and OrderService.

package myservice. service;

import org. springframework. context. annotation. Bean;


import org. springframework. context. annotation. Configuration;
import org. springframework. security. config. annotation. method
.configuration.EnableReactiveMethodSecurity;
import org. springframework. security. config. annotation. web
.reactive.EnableWebFluxSecurity;
import org. springframework. security. config. web. server
.ServerHttpSecurity;
import org. springframework. security. core. userdetails
.MapReactiveUserDetailsService;
import org. springframework. security. core. userdetails. User;
import org. springframework. security. core. userdetails
.UserDetails;
import org. springframework. security. web. server
.SecurityWebFilterChain;
import org. springframework. security. web. server. authentication
.RedirectServerAuthenticationSuccessHandler;

@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
class Config {

@Bean
@Bean
SecurityWebFilterChain springWebFilterChain(
ServerHttpSecurity http
) throws Exception {
http
.formLogin()
.authenticationSuccessHandler(
new RedirectServerAuthenticationSuccessHandler( "/graphi
);

return http
.csrf( ServerHttpSecurity.CsrfSpec:: disable)
.authorizeExchange( exchanges -> {
exchanges.anyExchange().authenticated();
})
.build();
}

@Bean
@SuppressWarnings( "deprecation")
MapReactiveUserDetailsService userDetailsService() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncod
UserDetails luna = userBuilder
.username( "Luna").password( "password")
.roles( "USER").build();
UserDetails andi = userBuilder
.username( "Andi").password( "password")
.roles( "USER", "ADMIN").build();
return new MapReactiveUserDetailsService( luna, andi);
}
}

package myservice. service;

import org. springframework. graphql. data. method. annotation


.Argument;
import org. springframework. graphql. data. method. annotation
.MutationMapping;
import org. springframework. graphql. data. method. annotation
.QueryMapping;
import org. springframework. stereotype. Controller;
import reactor. core. publisher. Mono;

import java. util. List;


@Controller
record OrderController( OrderService orderService) {

@QueryMapping
Mono< List< Order>> myOrders() {
return orderService.getOrdersForCurrentUser();
}

@MutationMapping
Mono< DeleteOrderPayload> deleteOrder(
@Argument DeleteOrderInput input) {
Mono< Boolean> booleanMono = orderService
.deleteOrder( input.orderId());
return booleanMono.map( DeleteOrderPayload::new);
}

package myservice. service;

import org. springframework. security. access. prepost. PreAuthorize;


import org. springframework. security. core. context
.ReactiveSecurityContextHolder;
import org. springframework. stereotype. Service;
import reactor. core. publisher. Mono;

import java. security. Principal;


import java. util. ArrayList;
import java. util. List;
import java. util. stream. Collectors;

@Service
class OrderService {

private final List< Order> orders;

OrderService() {
// A mutable list of orders
this.orders = new ArrayList<>( List.of(
new Order( "1", "Kibbles", "Luna"),
new Order( "2", "Chicken", "Skipper"),
new Order( "3", "Rice", "Luna"),
new Order( "4", "Lamb", "Skipper"),
new Order( "5", "Bone", "Luna"),
new Order( "6", "Toys", "Luna"),
new Order( "7", "Toys", "Skipper")
));
}

Mono< List< Order>> getOrdersForCurrentUser() {


return ReactiveSecurityContextHolder.getContext()
.map( securityContext -> {
Principal principal = securityContext.getAuthentication();
return orders
.stream()
.filter( order -> order.owner().equals( principal.getName
.collect( Collectors.toList());
});
}

@PreAuthorize( "hasRole('ADMIN')")
Mono< Boolean> deleteOrder( String orderId) {
return Mono.just( orders
.removeIf( order -> order.id().equals( orderId)));
}

Note that all authorization logic is now contained in the


OrderService.

In addition to @PreAuthorize, Spring Security also offers


@PostAuthorize, @PreFilter, and @PostFilter to perform authorization
and filtering before and after method invocation.

Testing auth
For an introduction to testing, please see the previous chapter on
Testing.
Let’s write end-to-end auth tests. To start with the simplest test and
establish a good baseline, let’s verify that we reject unauthenticated
requests.
package myservice. service;

import org. junit. jupiter. api. Test;


import org. springframework. beans. factory. annotation. Autowired;
import org. springframework. boot. test. autoconfigure. web. reactive
.AutoConfigureWebTestClient;
import org. springframework. boot. test. context. SpringBootTest;
import org. springframework. http. HttpStatus;
import org. springframework. http. MediaType;
import org. springframework. test. web. reactive. server
.WebTestClient;

import java. util. Map;

@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
class AuthE2ETest {

@Autowired
WebTestClient webTestClient;

@Test
void shouldRejectUnauthenticated() {
String document = "query orders { myOrders { id } }";
Map< String, String> body = Map.of( "query", document);
webTestClient
.mutateWith(
( builder, httpHandlerBuilder, connector)
-> builder.baseUrl( "/graphql"))
.post()
.contentType( MediaType.APPLICATION_JSON)
.accept( MediaType.APPLICATION_JSON)
.bodyValue( body)
.exchange()
.expectStatus().isEqualTo( HttpStatus.FOUND);
}
}
In this end-to-end test, we reject a GraphQL request with a 302
Found result, and redirect to another page (the login page).
Depending on the service, we could assert another HTTP status code
such as 401 Unauthorized.

If you prefer to test only the GraphQL layer, rather than the whole
service end-to-end, you can use WebGraphQlTester.

Let’s test that we return the correct orders for the authenticated
user.
package myservice. service;

import org. junit. jupiter. api. Test;


import org. springframework. beans. factory. annotation. Autowired;
import org. springframework. boot. test. context. SpringBootTest;
import org. springframework. context. annotation. Import;
import org. springframework. graphql. server. WebGraphQlHandler;
import org. springframework. graphql. server. WebGraphQlInterceptor;
import org. springframework. graphql. server. WebGraphQlRequest;
import org. springframework. graphql. server. WebGraphQlResponse;
import org. springframework. graphql. test. tester. WebGraphQlTester;
import org. springframework. security. authentication
.UsernamePasswordAuthenticationToken;
import org. springframework. security. core. authority
.SimpleGrantedAuthority;
import org. springframework. security. core. context
.ReactiveSecurityContextHolder;
import org. springframework. security. core. context. SecurityContext;
import org. springframework. security. core. context
.SecurityContextHolder;
import org. springframework. stereotype. Component;
import reactor. core. publisher. Mono;

import java. util. List;

@SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.MOC


@Import( WebGraphQlTest.WebInterceptor.class)
class WebGraphQlTest {

@Autowired
WebGraphQlHandler webGraphQlHandler;
@Component
static class WebInterceptor implements WebGraphQlInterceptor {

@Override
public Mono< WebGraphQlResponse> intercept( WebGraphQlRequest re
Chain chain) {
UsernamePasswordAuthenticationToken authenticated =
UsernamePasswordAuthenticationToken.authenticated(
"Luna", "password",
List.of(new SimpleGrantedAuthority( "ROLE_USER")));

SecurityContext context = SecurityContextHolder.getContext()


context.setAuthentication( authenticated);
return chain
.next( request)
.contextWrite(
ReactiveSecurityContextHolder
.withSecurityContext( Mono.just( context)
));
}
}

@Test
void testCorrectOrdersAreReturned() {
WebGraphQlTester webGraphQlTester
= WebGraphQlTester.create( webGraphQlHandler);
String document = "query orders { myOrders { id } }";
webGraphQlTester.document( document)
.execute()
.errors()
.verify()
.path( "myOrders[*].id")
.entityList( String.class)
.isEqualTo( List.of( "1", "3", "5", "6"));
// Luna's orders previously defined in the OrderService
}
}

We register a new WebInterceptor and create the authenticated user


because Spring Security relies on the SecurityContext, which itself is
being stored in the Reactor context. In the test itself, we verify that
we return no error and then check the order IDs in the response.
These order IDs were previously defined in the OrderService
constructor.

Within the same class, we can also test that an unauthorized user
cannot delete orders.
@Test
void testMutationForbidden() {
WebGraphQlTester webGraphQlTester
= WebGraphQlTester.create( webGraphQlHandler);
String document = """
mutation delete( $id: ID){
deleteOrder( input:{ orderId: $id}){ success}} """;
webGraphQlTester.document( document)
.variable( "id", "1")
.execute()
.errors()
.expect( responseError ->
responseError.getMessage().equals( "Forbidden") &&
responseError.getPath().equals( "deleteOrder")).verify()
}

Note how this test verifies a GraphQL error, not an HTTP status
code, because the overall HTTP response is a 200. We verify that the
message and the path match our expectation.

In this chapter, we covered how to secure GraphQL services with


Spring for GraphQL’s useful Spring Security integrations.
Java client
Spring for GraphQL comes with a client, GraphQlClient, for making
GraphQL requests over HTTP or WebSocket.

The HttpGraphQlClient is for queries and mutations, to execute


GraphQL requests over HTTP. The WebSocketGraphQlClient is for
subscriptions, and executes GraphQL requests over a shared
WebSocket connection.

We interact with the base Java interface GraphQlClient. We use


HttpGraphQlClient and WebSocketGraphQlClient to create specific
instances implementing the GraphQlClient interface. The design is
similar to GraphQlTester, which we discussed previously in the
Testing chapter.

HTTP client
The HTTP GraphQL client is basically a wrapper around a WebClient,
so we need to provide a WebClient when creating a HttpGraphQlClient.
package myservice. service;

import org. springframework. graphql. client. HttpGraphQlClient;


import org. springframework. web. reactive. function. client
.WebClient;

WebClient webClient = ... ;


HttpGraphQlClient graphQlClient = HttpGraphQlClient.create( webClien
Using builder methods, we can provide specific HTTP settings, such
as headers at build time.
HttpGraphQlClient graphQlClient = HttpGraphQlClient
.builder( webClient)
.header( "Special-Header", "true")
.build();

or
HttpGraphQlClient graphQlClient = HttpGraphQlClient
.builder( webClient)
.headers( httpHeaders -> httpHeaders.setBearerAuth( "token"))
.build();

We can also provide other settings such as url.


HttpGraphQlClient graphQlClient = HttpGraphQlClient
.builder( webClient)
.url( "/public/graphql")
.build();

Once created, we can’t change any of these client settings. For


different settings, we need to mutate the client and use the builder
methods again.
HttpGraphQlClient newClient = graphQlClient
.mutate()
.header( "Another-Header", "false")
.build();

WebSocket client
The WebSocketGraphQlClient uses a WebSocketClient under the hood.
We have to provide a WebSocketClient when creating a new
WebSocketGraphQlClient. Note that WebSocketClient is an abstraction
with implementations for Reactor Netty, Tomcat and others.

Here is an example using a Netty-based implementation.


import org. springframework. graphql. client. WebSocketGraphQlClient;
import org. springframework. web. reactive. socket. client
.ReactorNettyWebSocketClient;
import org. springframework. web. reactive. socket. client
.WebSocketClient;

String url = "http://localhost:8080/graphql";


WebSocketClient client = new ReactorNettyWebSocketClient();
WebSocketGraphQlClient graphQlClient = WebSocketGraphQlClient
.builder( url, client)
.build();

Note that in the WebSocket client we provide the URL via the
builder builder(url, client), whereas in the HTTP client it is set via
the builder url.

Once created, we can’t change any WebSocket client settings. For


different settings, we need to mutate the client and use the builder
methods again.
WebSocketGraphQlClient newClient = graphQlClient
.mutate()
.url( "https://my-new-url")
.build();

GraphQlClient
We can only use GraphQlClient after creating an instance of an HTTP
or WebSocket client. In the following examples, graphQlClient could
be either an HTTP or WebSocket client.

Use document or documentName to define the request operation and


then we can retrieve specific parts of the response. Note that this is
similar in design to GraphQlTester.
Mono< List< Pet>> pets = graphQlClient
.document( "query petNames {pets{name}}")
.retrieve( "pets")
.toEntityList( Pet.class);
If we have a subscription request, we use retrieveSubscription to get
Flux instead of a Mono.

Flux< Pet> pets = graphQlClient


.document( "subscription newPetNames {newPet{name}}")
.retrieveSubscription( "newPet")
.toEntity( Pet.class);

As an alternative to retrieve, we can use execute and


executeSubscription to return a Mono<ClientGraphQlResponse> or
Flux<ClientGraphQlResponse> respectively. A ClientGraphQlResponse
allows access to the full GraphQL response, including GraphQL
errors.
Mono< List< Pet>> pets = graphQlClient
.document( "query {pets{name}}")
.execute()
.map( response -> {
List< ResponseError> errors = response.getErrors();
// Your additional code here
});

In this chapter, we covered Spring for GraphQL’s built-in


GraphQlClient, for making GraphQL requests over HTTP or
WebSocket.

You might also like