Previously, I wrote an article covering how to create and test a Spring Boot app with GraphQL. At that time, it used an experimental
graphql-spring-boot-starter dependency but now it is officially released as
While I was at a webinar, Korea Spring Meetup with Josh Long, he introduced
@BatchMapping annotation which can help solving the n+1 problem. Let’s find out what it is about.
There are 3 different movies (movieInfos) and there are a total of 100 reviews. Each review has a movieInfoId which can be mapped to one of the three movies (1 movieInfo : n reviews relationship).
So the main goal is to serve a list of movieInfo with reviews.
First of all, here is the project setup. I used
spring-cloud-starter-gateway because I was testing a couple of things about gateway. It is not necessary to use this dependency. If you are taking out the gateway dependency, make sure it has
Note that spring boot version has to be newer than or equal to 2.7.0 because
spring-boot-starter-graphql is available from 2.7.0 and onwards.
Secondly, we need to define GraphQL schema. Make sure to add this
schema.graphqls file under
schema.graphqls file looks like this:
Lastly, here are the application properties. graphiql was enabled to make testing easier.
So here is the first approach.
This works perfectly but there will be n+1 calls.
- fetch movieInfos (returns 3 movieInfos)
- fetch reviews for movieInfoId 1
- fetch reviews for movieInfoId 2
- fetch reviews for movieInfoId 3
From this point,
reviews(..) method will change and the rest of the code will stay the same.
According to the GraphQL Java documentation,
dataloader can help with batching requests and caching per request.
java-dataloaderwill help you to make this a more efficient process by both caching and batching requests for that graph of data items. If
dataloaderhas seen a data item before, it will have cached the value and will return it without having to ask for it again.
@BatchMapping documentation describes how it registers a batch loader and returns from
The annotated method is registered as a batch loading function…
These clues gave strong evidences that
@BatchMapping annotation was indeed implemented to solve the n+1 issue.
However, it wasn’t clear about what return types are required or supported at the beginning. According to the documentation,
In addition to returning Mono<Map<K,T>>, an @BatchMapping method can also return Flux<T>. However, in that case the returned sequence of values must match the number and order of the input keys.
So here was my initial trial:
However, it was not very successful because the movieInfoId of the movieInfo and the reviews do not match. This can be solved using
flatMapSequential instead of
However, this approach seemed identical to the first approach. While I was watching this tutorial, this tutorial demonstrates the exact same case where the order of data gets mixed up due to the asynchronous nature. To solve the problem, the tutorial shows the difference between
registerMappedBatchLoader() and how
registerMappedBatchLoader() solves this issue.
While looking at the implementation of the
spring-boot-starter-graphql, I was able to find out
registerMappedBatchLoader() are called depending on the return type of the method.
Because I used
Flux<T>, it used
registerBatchLoader() which explains why movieInfo and reviews did not match appropriately. It also shows what return types are supported with
Lastly, I decided to use
Mono<Map<K, V>> return type and also implemented a new endpoint in Movie-Review service to take a list of movieInfoIds as input. I came to realize that I already have a list of movieInfos inside
@BatchMapping method which means I could combine 3 API calls into 1 API call by changing Movie-Review service implementation from
findByMovieInfoId(Long movieInfoId) to
We saw earlier from GraphQL Java documentation,
dataloader also supports caching (per request). This means if there was a data already fetched earlier during this same request, it will return from the cache instead making another HTTP call. In this example, each movieInfo or review is unique so there weren’t any benefits regarding caching.
This article covered how to make a simple Spring Boot app with GraphQL with
spring-boot-starter-graphql. It also covered how to make batching requests in order to solve n+1 problem.
@BatchMapping, I was able to get the same result as the first approach but with less HTTP calls.
Batching | GraphQL Java
If you are using graphql, you are likely to making queries on a graph of data (no surprises there). However, it's easy…