GraphQL with Java and Kotlin - Creating a GraphQL Server using Java and Kotlin

GraphQL is a query language that helps expose your data to clients in a flexible and iterative fashion. It is a well documented specification that can be used in any Java application or web framework. In this article we will explore the GraphQL Java implementation and develop a basic application that we can query in Java and Kotlin.

GraphQL with Java Project

To start we will create a basic gradle project that has two dependencies. We will reference the graphql-java implementation as well the basic kotlin dependencies. You can get a copy of the project by cloning the Github repo.

GraphQL Schema

The first thing we will do is define a schema. A schema in GraphQL defines your data types and how clients can interact with your graph. To create a schema we will create a new text file at src/main/resources/graphql/schema.graphql.

# src/main/resources/graphql/schema.graphql
type User {
    id: ID
    name: String!
    email: String!
}

type Query {
    users: [User]
}

Our schema has two types - a User type and a special type called Query. The Query type is reserved in GraphQL for defining entrypoints to query data. In this case our schema exposes the ability to query for users which will return a list of User objects.

GraphQL TypeDefinition and RuntimeWiringBuilder

The GraphQL Java implementation provides an implementation of the GraphQL specification that can be used in Java and Kotlin. We can import the library into our project by adding it to the dependencies block in our build.gradle file.

depdencies {
    implementation "com.graphql-java:graphql-java:12.0"
}

We can now import our schema into code and start to put together a GraphQL engine. We start by creating a new Kotlin class with a Main function so we can run our code. We then load the schema file we created earlier and instantiate some of the core GraphQL Java classes - a TypeRegistry, RuntimeWiring, and GraphQLSchema.

object Main {
    @JvmStatic
    fun main(args: Array<String>) {

        //Load the schema from the resources folder
        val schemaStream = this.javaClass.classLoader.getResourceAsStream("graphql/schema.graphql")

        val typeRegistry = schemaStream.use { stream ->

            //Read the bytes into a string
            val schemaString = String(stream.readBytes(), Charset.forName("UTF-8"))

            //Create and return a TypeRegistry
            SchemaParser().parse(schemaString)
        }
        
        val runtimeWiringBuilder = RuntimeWiring.newRuntimeWiring()
    }
}

When we parse our schema file using SchemaParser().parse(schemaString) we are asking the GraphQL Java implementation to read our schema file and parse it into a TypeDefinition. This TypeDefinition represents all of the types we defined in our schema file.

We then need to create a RuntimeWiring that maps to our TypeDefinition. The RuntimeWiring is responsible for mapping types defined in the TypeDefinition to code that fetches our data.

For example our TypeDefinition contains references to a User and its id, name, and email. All of this information will need to be mapped in our RuntimeWiring so that GraphQL knows how to fetch that information.

GraphQL RuntimeWiringBuilder, RuntimeWiring, and DataFetchers

Earlier we created a RuntimeWiringBuilder and we need to tell it how to fetch our data. The simplest way to do this is by defining a DataFetcher - note that these are sometimes called Resolvers or Field Resolvers in other language implementations.

We will define a User class, a list of User objects, and a DataFetcher that simply returns that in-memory list of data.

data class User(val id: UUID, val name: String, val email: String)

internal val ALL_USERS = listOf(
    User(UUID.randomUUID(), "Bob", "bob@graphql.com"),
    User(UUID.randomUUID(), "Jane", "jane@graphql.com"),
    User(UUID.randomUUID(), "Mary", "mary@graphql.com")
)

class UsersFetcher : DataFetcher<List<User>> {
    override fun get(environment: DataFetchingEnvironment?): List<User> {
        return ALL_USERS
    }
}

We now need to tell our RuntimeWiringBuilder about our DataFetcher. To do this we need indicate that this fetcher should be used when someone sends a request that contains the type Query and the field users. We then add that definition to the RuntimeWiringBuilder and call build() to generate a RuntimeWiring.

val queryType = TypeRuntimeWiring.newTypeWiring("Query")
queryType.dataFetcher("users", UsersFetcher())
            
runtimeWiringBuilder.type(queryType)
            
val runtimeWiring = runtimeWiringBuilder.build()            

GraphQL ExecutableSchema

With our TypeDefinition and RuntimeWiring built we can create a new GraphQLSchema that can be used to execute our queries.

val executableSchema = SchemaGenerator().makeExecutableSchema(typeRegistry, runtimeWiring)
val graphQLSchema = GraphQL.newGraphQL(executableSchema).build()

Querying our GraphQL Schema

At this point we can start executing queries against our schema. Note that the graphql-java implementation is simply a query service and engine. It does not have any code related to exposing your engine over HTTP because GraphQL as a specification does not lock you in to using HTTP or any other transport. You can utilize the engine we have created here and expose this via HTTP in any web application framework. If you are using spring boot you can check the kickstart or if you are using Vert.x you can check our there wrappers here.

Lets go ahead and run the users query we defined earlier. We will define a query in a string and pass that string to the schema so it can be executed.

val usersQuery = """
    query {
        users {
            id
            name
            email
        }
    }
""".trimIndent()

val result = graphQLSchema.execute(usersQuery)
println("Result: ${result.getData<Map<String, String>>()}")            

When the query executes we expect to get back a Map that contains a list of users with a key of users.

RESULT: {users=[{id=a45efbcd-654f-49bc-8da6-171b40034f9b, name=Bob, email=bob@graphql.com}, {id=71e28225-6a1c-43d9-9919-615e863e045d, name=Jane, email=jane@graphql.com}, {id=9a285f9f-1ba4-4e64-b4ce-2c92f87f6c6b, name=Mary, email=mary@graphql.com}]}

We can print these each of the users out so they are easier to see line by line with the following.

result.getData<Map<String, List<Object>>>()["users"]?.forEach { item ->
    println(item)
}

Which will give us some cleaner output that looks like.

{id=1718e1fe-3e68-42b7-a2e3-ea6c289e78ef, name=Bob, email=bob@graphql.com}
{id=11635633-8586-4d4f-875e-8622109fbd7e, name=Jane, email=jane@graphql.com}
{id=19135a1f-d178-412e-80b4-38db61533b9a, name=Mary, email=mary@graphql.com}

With the engine above you should now be able to implement a GraphQL server in your Java and Kotlin applications.