How to Create Graphql APIs Using Java

Creating GraphQL APIs Using Java

GraphQL is an API technology created by Facebook engineers in 2012 to address the over-fetching of information problem that comes with REST API implementations.

Usually, when you create a REST API, on rare occasions, you define the list of fields or information that is important to the stakeholder; usually, you take care only of the input parameters, resources, and methods to consume the API independently of other aspects like security or result format.

GraphQL is a query language that allows stakeholders to specify which information is fetched from the underlying services. This information is represented in a graph-like data structure. This data is defined in a strong type structure called a Schema. This Schema is composed of fields, types, arguments, directives, and operations.

Clean GraphQL query:

query salesByCountryFromLastYear($country: String, $year: Int) {
  departments(country: $country) {
    id
    name
    sales(year: $year) {
      id
      amount
      date
    }
  }
}

Variables

{
  "country": "Mexico",
  "year": "2023"
}

At the same time, like other specifications, we have primitive types to define strings, numbers, or binary values. Additionally, we can define objects (and their interfaces) and other data collections like lists and enums. Until this point, maybe you’re thinking that it is a copy of Swagger or any other API specification language.  

The power of GraphQL comes from the capability to choose the information that the client needs, but this capability is not limited only to using a subset of the fields of one Schema; you can also compose multiple queries in one operation.

To define this behavior, GraphQL uses operations and functions. Those functions are called Resolvers or DataFetchers. They are tied to a set of input arguments and a return type.

Finally, we can find all the concepts that we defined previously in the GraphQL request. A GraphQL request is an HTTP request that usually uses the POST method with a JSON body where we define the graph queries (yes, we can composite a request with multiple resolvers) and their input arguments and expected fields. Also, the request can include HTTP headers.

type Query {
  employersByCountry(country: String): [Employer]
  employerById(id: Int): Employer
}

type Employer {
  id: Int
  name: String
  country: String
  age: Int @deprecated(reason: "Use `birthday`.")
  birthday: Date
}

GraphQL APIs are agnostic, meaning that they are not tied to a specific programming language. For this example, we will use Java with a popular implementation developed by Netflix. Actually, GraphQL is one of Netflix’s biggest bets in its API architecture.

To use the Netflix Domain Graph Service Framework (DGS), you need Java 17 or Kotlin and SpringBoot 3.x. After creating your Java application, you only need to add the DGS dependencies and define a schema and a resolver class.

GraphQL is designed to be a schema/documentation-first API. It means that you need to have a schema document where you define the data types and operations before the code implementation.

The Schema could be split into multiple *.graphql files, which helps to create reusable types that you share across Java projects. At least you need one schema file in src/main/resources/schema/schema.graphqls path.

type Customer {
  id: Int
  name: String
  taxId: String
}

type Product {
  id: Int
  name: String
  price: Float
}

type Sale {
  id: Int
  total: Float
  date: String
  items: [Product]
  customer: Customer
}

type Query {
  getSalesById(id: Int): Sale
  getSalesByCustomer(customerId: Int): Sale
  getSalesByDate(startDate: String, endDate: String): [Sale]
}

The resolver is a Java class annotated with @DgsComponent. This enables an endpoint, which is equivalent to the @RestController annotations of a REST API. Then you need to define Java methods that will work as query operations using @DgsQuery annotation. Optionally, each method can receive arguments using @InputArgument annotation and a DgsDataFetchingEnvironment. The return type is a POJO class without any special features.

@DgsComponent
public class SalesResolver {

    @DgsQuery
    public Sale getSalesById(@InputArgument Integer id) {
        System.out.println(getRandomCustomer());
        return salesSample.stream()
                .filter(sale -> sale.getId().equals(id))
                .findFirst()
                .orElse(null);
    }
}

Given that GraphQL APIs run over HTTP, you can use any HTTP client to consume it. For example, you can use curl, Postman, or some GraphQL client/library. If you are using DGS in your backend application, it enables by default a GUI for test proposal that you can access through your web browser at http://localhost:8080/graphiql.

Reconstructed GraphQL query:

query GetSalariesById($saleId: Int) {
  getSalesById(id: $saleId) {
    id
    date
    total
  }
}

Variables:

{
  "saleId": 1001
}

Request body:

{
  "query": "query GetSalariesById($saleId: Int){ 
      getSalesById(id: $saleId){ 
        id 
        date 
        total 
      } 
    }",
  "variables": {
    "saleId": 1001
  }
}

Please keep in mind that the Request and Response section of the GraphQL interface parses and removes some details of the HTTP body.

A raw HTTP request looks like this:

POST /graphql HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Cookie:
Content-Length: 143

GraphQL is a mature technology that big companies have adopted, and it is widely used as the first API layer for building microservices across multiple platforms, primarily for web and mobile applications.

In this article, we introduced high-level concepts and usage of this technology; however, there are a lot of considerations to take care of to improve the provider application performance. A correct implementation of a resolver is the key to benefiting from GraphQL regardless of the programming language used to implement it.