GraphQL is a query language for dynamic queries on linked data. In the GraphQL world, there are many tutorials and documentation that illustrate the concrete introduction to GraphQL. Since getting started requires a lot of explanation, regular tutorials usually have a simple setup, which does not reflect the real world complexity. If additional technologies are added or the complexity of the project increases, these “Hello World” examples are usually no longer so helpful.
This article introduces GraphQL on the technology Spring Boot with JPA and Kotlin. I also present some best practices which I have learned while I have worked with it. You can find the source code here: https://github.com/akquinet/kotlin-spring-graphql
I will not explain the basics of GraphQL and the other technologies used here. Therefore, it is advisable be familiar with the following technologies:
- GraphQL (You should know what a schema looks like and how to query data)
- Spring Boot with JPA
- Kotlin
General Architecture
A Spring Boot application usually consists of 4 layers, with each layer only interacting directly with its adjacent one.

In this article, we will focus on the presentation layer. The application can be reached externally via this layer. Usually via a REST endpoint. GraphQL uses exactly one of these endpoints to receive a GraphQL query or mutation by POST method. The Persistence Layer uses JPA to communicate with the Database.
Basic Setup
To set up the application-skeleton (without GraphQL), the Spring Boot Starter Project was used:
- Kotlin (1.3.10)
- Spring-Boot-Starter (with JPA, Web) (2.1.6)
- Database MariaDB (2.4.2)
Demo application context
To demonstrate the GraphQL integration, the application has created a relationship between people and their pets in the persistence layer. Each person has exactly one address. Each person has multiple pets. See Entity.
@Entity class PersonEntity( var name: String, @OneToOne(cascade = [CascadeType.ALL], orphanRemoval = true, fetch = FetchType.LAZY) val address: AddressEntity, @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, mappedBy = "owner") val pets: List<PetEntity> = ArrayList(), @Id @GeneratedValue var id: Long? = null ) @Entity class AddressEntity( var street: String, var zipCode: String, var city: String, @Id @GeneratedValue var id: Long? = null ) @Entity class PetEntity( @JsonIgnore @ManyToOne(fetch = FetchType.LAZY) var owner: PersonEntity, var name: String, @Enumerated(EnumType.STRING) var type: PetType, @Id @GeneratedValue var id: Long? = null ) enum class PetType { CAT, DOG, BIRD }
Integrate GraphQL
To integrate GraphQL into a Spring Boot application, the following steps are necessary:
- Add GraphQL dependencies
- Configure Spring application
- Create GraphQL schema
- Create a GraphQL resolver
1. Add GraphQL dependencies
Similar to the Spring-Boot-starter project there is also a Graphql-Spring-Boot-starter project, in which all necessary / common dependencies from Spring Boot and GraphQL are included (see dependencies). The GraphQL-Spring-Boot-Starter project creates a Spring Boot application with spring-boot-starter-websocket
, spring-boot-starter-web
and spring-boot-starter-actuator
. Of course it also contains graphql-java-servlet
dependencies. The configuration magic of GraphQL is done by graphql-java-tools
and thus ensures that minimal boilerplate code as possible is needed. This also includes using the schema-first pattern. To do this, the application loads the GraphQL schema at startup and provide the associated GraphQL endpoint. The alternative would be the hard-to-read schema-builder in the code. Afterwards all classes are searched for data resolvers, validated them against the already loaded schema, and make them available as beans.
To use this, the following dependencies must be added in build.gradle.kts:
dependencies { compile("com.graphql-java-kickstart:graphql-spring-boot-starter:5.10.0") compile("com.graphql-java-kickstart:altair-spring-boot-starter:5.10.0") }
Be sure to use Kotlin Version >= 1.3.10, see doc.
Similar to Swagger GraphQL has an UI available to see the interface definition. This UI can be used to view the schema definition as well as sending requests to the server. I opted for the alternative UI altair instead of using the regular GraphiQL UI, as altair offers more functionality. For example, altair can also set headers for authorization, which is not possible with GraphiQL.
Here an example of the Altair-UI:

2. Configure Spring application
Next, the application must be configured for GraphQL. This requires the following configuration in the application.yml:
graphql: servlet: mapping: /graphql enabled: true corsEnabled: false exception-handlers-enabled: true tools: schema-location-pattern: "**/*.graphqls" # Enable or disable the introspection query. Disabling it puts your server in contravention of the GraphQL # specification and expectations of most clients, so use this option with caution introspection-enabled: true altair: enabled: true mapping: /altair endpoint.graphql: /graphql pageTitle: Altair props.resources.variables: graphqls/altair/variables.graphql
GraphQL explanations:
mapping: /graphql
defines the REST-endpoint used to execute the GraphQL queries.schema-location-pattern: " **/*.Graphqls"
sets the path to the schema files.
Altair explanations:
mapping: /altair
defines the path to the GraphQL altair-UI.endpoint.graphql: /graphql
path where the application hosts the GraphQL-endpointprops.resources.variables: graphqls/altair/variables.graphql
defines the path to a file with predefined variables for the queries in the altair-UI.
If the variables file does not exist, the default value in the Altair UI is an invalid value for the variables, so it must first be changed before a request can be sent. Therefore, it is recommended to set a file with default variables, which can be empty.
3. Create GraphQL schema
After the Spring Boot application has been configured for GraphQL, the next step is creating the GraphQL schema. It must be located in a file under the graphql.tools.schema-location-pattern
path defined in the previous step.
The following schema describes the demo application context with persons and their address and pets:
type Query { persons: [Person!]! person(id: ID!): Person } type Person { id: ID! name: String address: Address pets: [Pet!]! } type Address { id: ID! street: String zipCode: String city: String } enum PetType { CAT DOG BIRD } type Pet { id: ID! owner: Person! name: String! reverseName: String type: PetType! }
Here is quick overview in the GraphQL schema syntax:
- Possible GraphQL-operations are defined with these types:
Query
orMutation
. All other defined types like Person or Pet only to structure data. TheQuery
-Operation fetches data andMutation
-Operation writes data. The fields in those operation-types represents all possible GraphQL-entrypoints. It is mandatory to implement at least one type of them, otherwise you can’t interact with GraphQL. In the example thepersons
field is used for querying all persons. - GraphQL supports the following base types:
String
,Int
,Float
,Boolean
andID
. Types are written after a field and separated with a “:
“. Every other type must be defined with thetype
keyword. E.g. the custom types Person or Pet. - Fields with braces represents data with related params (e.g. only a Person with a given ID).
[...]
defines a list of objects with the given type.!
ensures the value is not null.
With this GraphQL-schema we can execute the following query, which uses the Query
-Operation and ask for all persons:
query { persons { name } }
Response:
{ "data": { "persons": [ { "name": "Alice" }, { "name": "other name" } ] } }
4. Create a GraphQL resolver
Now let’s take a look, how to implement the schema definitions in the Spring application. For this you have to implement different resolver types:
- The root type
Query
needs an implementation ofGraphQLQueryResolver
- The root type
Mutation
needs an implementation ofGraphQLMutationResolver
- Each self.defined type has to implement the
GraphQLResolver
The root type implementations GraphQLQueryResolver
or GraphQLMutationResolver
must implement all methods with the field-names the schema defines. Here is an example implementation of the GraphQLQueryResolver
for the GraphQL-schema in the previous step:
@Component class PersonQueryResolver @Autowired constructor(private val personRepository: PersonRepository) : GraphQLQueryResolver { fun persons(): List<PersonEntity> = personRepository.findAll() fun person(id: Long): PersonEntity = personRepository .findById(id) .orElseThrow { Exception("Can not find person with id: $id") } }
For the other custom types you need to implement GraphQLResolver
. With the GraphQLResolver
it is not necessary to implement all fields. If a method is missing the default PropertyDataFetcher
fetches the field with the same name from the implemented resolver entity-type. For example for type Person:
// simplified person entity without persistence annotations class PersonEntity( var name: String, val address: AddressEntity, val pets: List<PetEntity> = ArrayList(), var id: Long? = null ) @Component class PersonResolver : GraphQLResolver<PersonEntity> { // not necessary implementation for the person-schema-field name fun name(person: PersonEntity): String = person.name }
In this example, this GraphQLResolver
does not need to implement any resolver method (like name
), because of the same field-names between the schema-type Person and the PersonEntity. Of course it is possible to override the default DataFetcher.
Let’s take a look at an modified schema with the field reverseName
for a Person:
type Person { ... reverseName: String! }
Now you must implement a reverseName
method in the resolver, because the PersonEntity
does not have a field named like this:
@Component class PersonResolver : GraphQLResolver<PersonEntity> { fun reverseName(person: PersonEntity): String { return person.name.reversed() } }
Finally, the Spring Boot application can be started and requests made with the Altair UI (http://localhost:8080/altair
).

CONCLUSION
Compared to a REST interface, GraphQL offers many advantages. Especially for the client, as this gives it the freedom to send dynamic queries to the server. But in the backend much more development effort for the GraphQL interface has to be included. GraphQL is more complex in the backend than just a simple REST interface.
GraphQL is especially worthwhile for applications with many entities with many relations between each another. In doing so, it would be more and more time-consuming to implementing all necessary REST endpoints. With REST it is also often necessary to query several REST endpoints for a query.
By using the Graphql-Spring-Boot-starter project, the project setup has been made very easy to integrate GraphQL into Spring.
Thus, depending on the application area, it should be decided whether the extra effort by GraphQL is worthwhile.
The proof of concept of GraphQL with Spring Boot, JPA and Kotlin is verified by this article.
The next steps should be to take a deeper look how to work with GraphQL:
- How to write GraphQL-Tests
- How to handle GraphQL-Errors
- Are there some best practices?