If you are building a GraphQL application with Spring, we recommend using the official Spring for GraphQL integration. This integration is a collaboration between the Spring and GraphQL Java teams, and is maintained by the Spring team. In May 2022, Spring for GraphQL 1.0 GA was released.
Before the official Spring for GraphQL integration was released, there were many other GraphQL integrations for Spring, including the similarly named GraphQL Java Spring project from the GraphQL Java team, published under the com.graphql-java and com.graphql-java-kickstart group IDs. Many tutorials are still referring to this unrelated project.
Please use the official integration named "Spring for GraphQL", published under org.springframework and related group IDs.
We are happy to announce the availability of GraphQL Java 17.0.
See 17.0 release notes for all the details.
At the same time we wanted to give an update regarding our LTS (Long Term Support) policy.
Previously we maintained a LTS version of 9.x and after quite some time we announced 14.x as the next LTS.
The reality is that we didn't maintain 14.x really as a LTS version (we only released one bugfix release).
This was mainly caused by the minimal community feedback and our limited time and resources.
Going forward we decided to no offer any LTS versions anymore. We will only actively maintain and bugfix
the latest version (currently 17). We may backport critical bugfixes, but we are not committed to it.
If this is a huge problem for you or your Company and you are willing to help us with maintaining a LTS
version you can reach us via email at
hello at graphql-java dot com.
Every once in a while somebody asks which version of the GraphQL spec
GraphQL Java supports. The general answer is: the current draft.
The bigger question behind this is: what is the information you want get out of this question?
Why do you ask this question?
The thing is: spec releases are not really important and people misinterpret what they mean.
Evolution of the spec process
The GraphQL spec has five releases so far:
two in 2015 (including the first published version)
two in 2016
one in 2018
As you can see in the first two years spec releases where quite frequently, but after the one in 2018,
there has not been a release.
2017 was also the year the GraphQL Working Group was established.
This group is the main forum to evolve the spec since then. Over time this group established a very high bar
for every PR to be merged into the spec. (See the Contributing guidelines)
With this high standard set, nearly all implementations (including GraphQL Java) started to implement every
merged PR instead of waiting for a big release. Because they are very confident this change will be released
in this form, it is safe to implement it right away.
This treatment of merged PRs as de-factor releases is now an established rule in the GraphQL community.
This explains why the whole GraphQL ecosystem has evolved a lot since 2018, even without a release.
A release is not needed anymore if every merged PR is like a mini release.
I personally hope that we make this de-facto rule, that evey PR is a mini release, more official.
We should not use the word "draft" any more, but every merged PR should automatically result in a
new GraphQL spec version which is formally approved by the GraphQL TSC.
Coming back to the question: "Which spec version of GraphQL is supported"?
I hope by now it is clear why this question is probably not really helpful.
It is better to think about certain features you want to discuss instead referring to the spec releases.
We follow a fundamental rule in GraphQL Java regarding Threads: GraphQL Java never creates
Threads or interacts with Thread pools. We do this because we want to give the user the full control
and whatever GraphQL Java would do, it would not be correct for every use case.
Additionally to being strictly unopinionated regarding Threads, GraphQL Java is also fully reactive,
implemented via CompletableFuture (CF).
These two constrain together mean we rely on the CF returned by the user.
Specifically we piggyback on the CF returned by the DataFetcher
(or other async methods which can be implemented by the user, but we focus here on DataFetcher
as it is by far the most important).
// Pseudo code in GraphQL Java CompletableFuture<Object> dataFetcherResult = invokeDataFetcher(); dataFetcherResult.thenApply(result -> { // in which Thread where this code happens is controlled by the CF returned continueExecutingQuery(result); });
Blocking DataFetcher
Lets assume you are accessing a DB in a blocking way in your DataFetcher:
String get(DataFetchingEnvironment env) { return getValueFromDb(env); // blocking the Thread until the value is read from DB };
This is not completely wrong, but not recommend in general as the consequence of this kind of DataFecher
is that GraphQL can't execute the query in the most efficient way.
For example for the following query:
{ dbData1 dbData2 dbData3 }
If the DataFetcher for these dbData fields don't return a CF,
but block the Thread until the data is read, GraphQL Java will not work with maximum efficiency.
GraphQL Java can invoke the DataFetcher for all three fields in parallel. But if your DataFetcher for
dbData1 is blocking, GraphQL Java will also be blocked and only invoke the next DataFetcher once dbData<n>
is finished.
The recommend solution to this problem is offloading your blocking code onto a separate Thread pool
as shown here:
This code will maximize the performance and will cause all three fields to be fetched in parallel.
Different pools for different work
The subsequent work done by GraphQL Java will be executed in the same dbThreadPool until it
encounters a new DataFetcher returned by the user code and this new CF dedicates the Thread
for the subsequent work.
If you want to have separate pools for different kind of work, one for the actual DataFetcher which normally
involve IO and one of the actual GraphQL Java work (which is pure CPU), you need to switch back from your offloaded
pool to a dedicated GraphQL Java pool before returning the CF. You can achieve this with code like this:
Notice the .handleAsync which doesn't do anything except forwarding the result, but on a
different pool (graphqlJavaPool).
This way you have different pools for different kind of work (one for CPU bound GraphQL Java work and one
for multiple ones for IO bound work), which can be configured and monitored independently.
In a fully reactive system
If your system is fully reactive your DataFetcher will more look like this
Because the code is non blocking there is no need to offload anything on a dedicated Thread pool to avoid blocking
GraphQL Java.
You still might want to consider using a dedicated GraphQL Java pool as you otherwise would use
Threads which are dedicated to IO. How much this is really relevant depends highly on your use case.
For example Async Http Client (AHC) uses by default 2 * #cores (this value comes actually from Netty) Threads. If you
don't use a dedicated Thread Pool for GraphQL Java you might encounter situations under load where all AHC
Threads are either busy or blocked by GraphQL Java code and as a result your system is not as performant as it
could be. Normally only load tests in production like environments can show the relevance of different Thread pools.
Today we are looking into the graphql.schema.DataFetchingFieldSelectionSet and graphql.execution.DataFetcherResult objects as means
to build efficient data fetchers.
The scenario
But first lets set the scene. Imagine we have a system that can return issues and the comments on those issues
{ issues { key summary comments { text } } }
Nominally we would have a graphql.schema.DataFetcher on issues that returns a list of issues and one on the field comments that returns the list of comments
for each issue source object.
As you can see this naively creates an N+1 problem where we need to fetch data multiple times, one for each issue object in isolation.
We could attack this using the org.dataloader.DataLoader pattern but there is another way which will discuss in this article.
Look ahead via DataFetchingFieldSelectionSet
The data fetcher behind the issues field is able to look ahead and see what sub fields are being asked for. In this case it knows that comments are being asked
for and hence it could prefetch them at the same time.
graphql.schema.DataFetchingEnvironment#getSelectionSet (aka graphql.schema.DataFetchingFieldSelectionSet) can be used by data fetcher code to get the selection set of fields for a given parent field.
Imagine this is backed by an SQL system we might be able to use this field look ahead to produce the following SQL
SELECT Issues.Key, Issues.Summary, Comments.Text FROM Issues INNERJOIN Comments ON Issues.CommentID=Comments.ID;
So we have looked ahead and returned different data depending on the field sub selection. We have made our system more efficient by using look ahead
to fetch data just the 1 time and not N+1 times.
Code Challenges
The challenge with this code design is that the shapes of the returned data is now field sub selection specific. We needed a IssueAndCommentsDTO for one sub selection
path and a simpler IssueDTO for another path.
With enough paths this becomes problematic as it adds new DTO classes per path and makes out child data fetchers more complex
Also the standard graphql pattern is that the returned object becomes the source ie. graphql.schema.DataFetchingEnvironment#getSource of the next child
data fetcher. But we might have pre fetched data that is needed 2 levels deep and this is challenging to do since each data fetcher would need to capture and copy
that data down to the layers below via new TDOs classes per level.
Passing Data and Local Context
GraphQL Java offers a capability that helps with this pattern. GraphQL Java goes beyond what the reference graphql-js system gives you where the object you
returned is automatically the source of the next child fetcher and that's all it can be.
In GraphQL Java you can use well known graphql.execution.DataFetcherResult to return three sets of values
data - which will be used as the source on the next set of sub fields
errors - allowing you to return data as well as errors
localContext - which allows you to pass down field specific context
When the engine sees the graphql.execution.DataFetcherResult object, it automatically unpacks it and handles it three classes of data in specific ways.
In our example case we will be use data and localContext to communicate between fields easily.
If you look now you will see that our data fetcher returns a DataFetcherResult object that contains data for the child data fetchers which is the
list of issueDTO objects as per usual. It will be their source object when they run.
It also passes down field specific localContext which is the pre-fetched comment data.
Unlike the global context object, local context objects are passed down from a specific field to its children and are not shared across to peer fields. This means
a parent field has a "back channel" to talk to the child fields without having to "pollute" the DTO source objects with that information and it is "local" in the sense
that it given only to this field and its children and not any other field in the query.
Now lets look at the comments data fetcher and how it consumes this back channel of data
Notice how it got the issueDTO as its source object as expected but it also got a local context object which is our pre-fetched comments. It can choose
to pass on new local context OR if it passes nothing then the previous value will bubble down to the next lot of child fields. So you can think of localContext
as being inherited unless a fields data fetcher explicitly overrides it.
Our data fetcher is a bit more complex because of the data pre-fetching but 'localContext' allows us a nice back channel to pass data without modifying our DTO objects
that are being used in more simple data fetchers.
Passing back Errors or Data or Both
For completeness we will show you that you can also pass down errors or data or local context or all of them at once.
It is perfectly valid to fetch data in graphql and to ALSO send back errors. Its not common but its valid. Some data is better than no data.