GraphQL with Java and Spring Boot

Tiago Albuquerque
10 min readJan 8, 2020

Implementing a GraphQL server with Java and Spring Boot

In this article I will give a brief introduction to GraphQL concepts and describe the process of implementing a graphql server using Java and Spring Boot framework.

GraphQL is a query language for APIs that was first built and used inside Facebook in 2012. After it was released as a specification.

So it is a query language to retrieve and update data from a server, describing the data of the API through a schema that contains the types and relationships between them.

Unlike REST APIs, that identify each resource by an URI and proceed operations over that resource using HTTP methods (GET, POST, PUT, DELETE…), GraphQL operations describes the data to fetch through a request defined by a schema.

A common example to explain the difference is, when you fetch data from a REST api server, the response contains all the data provided by the service, even if you don’t need all of them. In some cases we just need to grab the ‘id’ of all the response body of the resource. So sometimes it is created many ‘get methods/urls’ overloading the same resource with slightly different set of attributes demanded by each client. If there is a change in the resource structure, chances are that the clients doesn’t get aware and starts getting errors.
On the other hand, when you fetch data using GraphQL, you have to specify the schema, so it is possible to get only the set of attributes you need. Using the type and the defined schema, there is also less issues involving data parsing problems and stuff like that.

I think there isn’t a better protocol to follow (i.e., no silver bullet!), it will always depend on the context and domain of your application. It seems to me (personal opinion warning!!!) that to serve data to front-end clients, it feels better to provide a GraphQL API (like a façade), while the microservices in the back-end can communicate to each other using REST protocol (HTTP requests), for example.

GraphQL usually group operations in two types:
Queries (to fetch data) and Mutations (to create, update, and delete data).

Let's learn by doing!

Let's build above a graphql server of TV Series and its main characters, using Java (version 8) and Spring Boot framework.

To get more realistic, it will be implemented the persistence layer, and the entities will have an aggregation/dependency between them to show how to map that in graphql schema.

I'll be using Spring Boot because there is a lot of boilerplate code that does the parsing and “glueing” stuff, connecting the model to the graphql schema, but the intention here is to focus on the domain logic implementation and how to design data to be provided through the API.

The full project at github is available here.

  1. Initialize the project

To get started, we need to create a project in the Spring Initialzr home page.

I recommend to generate a simple project and add the dependencies after importing the project in the IDE, like shown below.

Spring project initialzr

We will need some libraries to deal with graphql schemas:

  • graphql-spring-boot-starter: Spring Boot starter for GraphQL projects, including ‘graphql-java’ library that implements the GraphQL specification.
  • graphql-java-tools: Libraries to parse GraphQL schemas, mapping them automatically to Java objects.
  • graphiql-spring-boot-starter (with an ‘i’ in the ‘graphiql’ word): Provides a visual tool (web page) to test our application.

We will also need the ‘spring-boot-starter-data-jpa’ to use Spring Data and H2 database dependency to store data locally.

The dependencies content of ‘pom.xml’ file is listed below, and complete version is available here:

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- graphql spring boot starter -->
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>5.0.2</version>
</dependency>
<!-- libraries to parse the GraphQL Schema -->
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>5.2.4</version>
</dependency>
<!-- graphiql tool -->
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphiql-spring-boot-starter</artifactId>
<version>5.0.2</version>
</dependency>
<!-- spring data -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- h2 embedded database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>

In order to enable the embedded H2 database it is necessary to input these lines at ‘application.properties’ file:

spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

2. Model and Persistence

Now, let's get the model and persistence layer done.

We will need to create the TV ‘Series’ and ‘Characters’ model classes, as well DAO classes to manage persistence.

// Series.java
@Entity

public class Series implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "seasons")
private Integer nrOfSeasons;
public Series() { } public Series(String name, Integer nrOfSeasons) {
this.name = name;
this.nrOfSeasons = nrOfSeasons;
}
// equals() and hashcode() implemented below omitted ...
// getter's and setter's implemented below omitted ...
}
// SeriesCharacter.java
@Entity
public class SeriesCharacter implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "nickname", nullable = false)
private String nickname;
@Column(name = "occupation", nullable = false)
private String occupation;
@Column(name = "birthday")
private LocalDate dateofbirth;
@ManyToOne
@JoinColumn(name = "id_series", nullable = false, updatable = false)
private Series series;
public SeriesCharacter() {} public SeriesCharacter(String name, String nickname, String occupation, LocalDate dateofbirth) {
this.name = name;
this.nickname = nickname;
this.occupation = occupation;
this.dateofbirth = dateofbirth;
}
// equals() and hashcode() implemented below omitted ...
// getter's and setter's implemented below omitted ...
}

DAO classes using spring data:

// SeriesDao.java
@Repository
public interface SeriesDao extends JpaRepository<Series, Integer> { }
// CharacterDao.java
@Repository
public interface CharacterDao extends JpaRepository<SeriesCharacter, Integer> { }

For better separation of concerns, let's also create a ‘Service’ class to provide methods for creating and retrieving data using the injected data access classes:

// SeriesService.java 
@Service
public class SeriesService {
@Autowired
private CharacterDao characterDao;
@Autowired
private SeriesDao seriesDao;
@Transactional
public Series createSeries(String name, Integer nrOfSeasons) {
Series series = new Series(name, nrOfSeasons);
return seriesDao.save(series);
}
@Transactional(readOnly = true)
public List<Series> getSeries() {
return seriesDao.findAll();
}
@Transactional(readOnly = true)
public Series getSeries(Integer id) {
Optional<Series> series = seriesDao.findById(id);
return series.orElseThrow(() -> new IllegalArgumentException("Series 'id' not found!!!") );
}
@Transactional
public SeriesCharacter createCharacter(String name, String nickname, String occupation, LocalDate dateofbirth, Integer seriesId) {
SeriesCharacter character = new SeriesCharacter(name, nickname, occupation, dateofbirth);
Series series = getSeries(seriesId);
character.setSeries(series);
return characterDao.save(character);
}
@Transactional(readOnly = true)
public List<SeriesCharacter> getCharacters() {
return characterDao.findAll();
}
@Transactional(readOnly = true)
public SeriesCharacter getCharacter(Integer id) {
Optional<SeriesCharacter> characterOpt = characterDao.findById(id);
return characterOpt.orElseThrow(() -> new IllegalArgumentException("Character 'id' not found!!!"));
}
}

3. GraphQL Schema

GraphQL has its own type system that is used to define the schema of an API. The syntax for writing schemas is called Schema Definition Language (SDL).

The schema describes the Types provided as well as Queries and Mutations available to the endpoint of the API.

Spring Boot looks for graphql schemas in classpath at files with extension as “.graphqls”. So, it is usually created a ‘graphql’ directory under “src/main/resources”, and put there our schemas files. Let's create a file named “schema.graphqls” (it could be any name with .graphqls extension) like this (comma at the end of fields lines are optional):

type Series {
id: ID!,
name: String,
nrOfSeasons: Int
}
type SeriesCharacter {
id: ID!,
name: String,
nickname: String,
occupation: String,
dateofbirth: String,
series: Series
}
type Query {
characters:[SeriesCharacter]
character(id: ID):SeriesCharacter
allSeries:[Series]
series(id: ID):Series
}
type Mutation {
createSeries(name: String!, nrOfSeasons: Int!):Series
createCharacter(name: String!, nickname: String!, occupation: String; birthday: String, seriesId: Int):SeriesCharacter
}

In the schema above, we created the ‘Series’ and ‘SeriesCharacter’ types with predefined fields types. Those are like the ‘resources’ in the REST api approach (not a good analogy, but I use it for better understanding).

And then we define the Queries (type Query) available to read data from server, and Mutation (type Mutation) methods to write (create,update,delete) data. Note that it is also defined typed arguments in mutations, and ours ‘Series’ and ‘SeriesCharacter’ types created before are used as fields and as return values of operations.

Now, we need to implement the resolver classes to map the fields in their respective root types and operations, implementing the GraphQLQueryResolver and GraphQLMutationResolver. These objects define the root GraphQL objects.

// Mutation.java
@Component
public class Mutation implements GraphQLMutationResolver {
@Autowired
private SeriesService service;
public Series createSeries(String name, Integer nrOfSeasons) {
return service.createSeries(name, nrOfSeasons);
}
public SeriesCharacter createCharacter(String name, String nickname, String occupation, String birthday, Integer seriesId) {
LocalDate dayOfBirth = LocalDate.parse(birthday, DateTimeFormatter.ISO_DATE);
return service.createCharacter(name, nickname, occupation, dayOfBirth, seriesId); }
}
}
// Query.java
@Component
public class Query implements GraphQLQueryResolver {
@Autowired
private SeriesService service;
public List<SeriesCharacter> characters() {
return service.getCharacters();
}
public SeriesCharacter character(Integer id) {
return service.getCharacter(id);
}
public List<Series> allSeries() {
return service.getSeries();
}
public Series series(Integer id) {
return service.getSeries(id);
}
}

The graphql-java library supports the following types:
Scalar (String, Boolean, Int, Float, ID, Long, Short, Byte, Float, BigDecimal, BigInteger), Object, Interface, InputObject and Enums.

For basic types like String and Numbers, the root resolver use the getter's and setter's methods of the entities (by its names) to map them to the target class. But when we have an object as a field (like the ‘Series’ field in the ‘SeriesCharacter’ type), we need to implement a specific resolver class to map this field, implementing the GraphQLResolver<T> interface:

// SeriesCharacterResolver.java
@Component
public class SeriesCharacterResolver implements GraphQLResolver<SeriesCharacter> {
@Autowired
private SeriesService seriesService;
public Series getSeries(SeriesCharacter character) {
return seriesService.getSeries(character.getSeries().getId());
}
}

4. Testing the application

And now we are finally ready!

Let's start our application by running its main class, and accessing the url “http://localhost:8080/graphiql” in the browser. We should see a page to test our Queries and Mutations, as well the documentation tab that reflects our schema file:

GraphiQL page

We can now input data into our server and retrieve them following the schema defined.

All the mutations and queries used in this example are listed in the “mutationsAndQueries.txt” file commited at the ‘resources’ directory of the github project.

I am going to list below some examples to input data and test the application, but feel free to explore the resources available by making use of the autocomplete feature of the GraphiQL page.

Mutations to insert a new Series:

mutation {
createSeries(name: "Friends", nrOfSeasons: 10)
{
id
name
nrOfSeasons
}
}

In this operation, we called the ‘createSeries’ operation passing the expected arguments types, and from the response (which will be a Series type) we asked for the ‘id’, ‘name’ and ‘nrOfSeason’ fields back (it could be less fields, if we wanted to). So the result will look like this:

{
"data": {
"createSeries": {
"id": "1",
"name": "Friends",
"nrOfSeasons": 10
}
}
}

Yet another Series creation and its result:

Graphiql example with Mutation of Series creation

We can populate the characters data by using the mutations listed below (one at a time). The result from each one will be a ‘SeriesCharacter’ type, from which we asked to fetch back its ‘id’, ‘name’ and the series ‘name’ (again, we could choose any set of the fields available to be retrieved):

mutation {
createCharacter(name: "Chandler Bing", nickname: "Ringo", occupation: "Advertising Copywriter", birthday: "1968-04-08", seriesId: 1) {
id,
name,
series {
name
}
}
}
mutation {
createCharacter(name: "Rachel Karen Greene", nickname: "Rach", occupation: "Executive", birthday: "1969-05-05", seriesId: 1) {
id,
name,
series {
name
}
}
}
mutation {
createCharacter(name: "Ross Geller", nickname: "Rossie", occupation: "Paleontologist", birthday: "1967-10-18", seriesId: 1) {
id,
name,
series {
name
}
}
}
mutation {
createCharacter(name: "Phoebe Buffay", nickname: "Pheebs", occupation: "Masseuse", birthday: "1966-02-16", seriesId: 1) {
id,
name,
series {
name
}
}
}
mutation {
createCharacter(name: "Joey Tribbiani", nickname: "Joe", occupation: "Actor", birthday: "1968-12-01", seriesId: 1) {
id,
name,
series {
name
}
}
}
mutation {
createCharacter(name: "Homer Simpson", nickname: "Hommie", occupation: "Safety Inspector", birthday: "1970-01-01", seriesId: 2) {
id,
name,
series {
name
}
}
}
mutation {
createCharacter(name: "Marjorie Jacqueline Simpson", nickname: "Marge", occupation: "Housewife", birthday: "1970-01-01", seriesId: 2) {
id,
name,
series {
name
}
}
}
mutation {
createCharacter(name: "Bartholomew JoJo Simpson", nickname: "Bart", occupation: "Student", birthday: "1990-01-01", seriesId: 2) {
id,
name,
series {
name
}
}
}
mutation {
createCharacter(name: "Lisa Marie Simpson", nickname: "Lisa", occupation: "Student", birthday: "1991-01-01", seriesId: 2) {
id,
name,
series {
name
}
}
}
mutation {
createCharacter(name: "Margaret Evelyn Simpson", nickname: "Maggie", occupation: "None", birthday: "1995-01-01", seriesId: 2) {
id,
name,
series {
name
}
}
}

And some queries to test the result data shown below (my favorite feature):

query {
allSeries {
id
name
}
}
Querying all inserted Series and retrieving just the ‘id’ and ‘name’ fields
query {
characters {
name,
nickname,
series {
name
}
}
}
Querying all inserted Characters and retrieving its ‘name’, ‘nickname’ and ‘series name’
query {
series(id: 1) {
id
name
}
}
Querying specific Series by id and retrieving ‘id’ and ‘name’ fields
query {
character(id: 5) {
id
name
nickname
series {
name
}
}
}
Querying specific Character by id and retrieving some fields from it

5. Complete project

The complete project is available in github here.

--

--