Skip to main content
Version: 5.x.x

Async Models

By default, graphql-kotlin-schema-generator will resolve all functions synchronously, i.e. it will block the underlying thread while executing the target function. While you could configure your GraphQL server with execution strategies that execute each query in parallel on some thread pools, instead we highly recommend to utilize asynchronous programming models.


graphql-kotlin-schema-generator has built-in support for Kotlin coroutines. Provided default FunctionDataFetcher will automatically asynchronously execute suspendable functions and convert the result to CompletableFuture expected by graphql-java.


data class User(val id: String, val name: String)
class Query {
suspend fun getUser(id: String): User {
// Your coroutine logic to get user data

will produce the following schema

type Query {
getUser(id: String!): User
type User {
id: String!
name: String!


graphql-java relies on Java CompletableFuture for asynchronously processing the requests. In order to simplify the interop with graphql-java, graphql-kotlin-schema-generator has a built-in hook which will automatically unwrap a CompletableFuture and use the inner class as the return type in the schema.

data class User(val id: String, val name: String)
class Query {
fun getUser(id: String): CompletableFuture<User> {
// Your logic to get data asynchronously

will result in the exactly the same schema as in the coroutine example above.


If you want to use a different monad type, like Single from RxJava or Mono from Project Reactor, you have to:

  1. Create custom SchemaGeneratorHook that implements willResolveMonad to provide the necessary logic to correctly unwrap the monad and return the inner class to generate valid schema
class MonadHooks : SchemaGeneratorHooks {
override fun willResolveMonad(type: KType): KType = when (type.classifier) {
Mono::class -> type.arguments.firstOrNull()?.type
else -> type
} ?: type
  1. Provide custom data fetcher that will properly process those monad types.
class CustomFunctionDataFetcher(target: Any?, fn: KFunction<*>, objectMapper: ObjectMapper) : FunctionDataFetcher(target, fn, objectMapper) {
override fun get(environment: DataFetchingEnvironment): Any? = when (val result = super.get(environment)) {
is Mono<*> -> result.toFuture()
else -> result
class CustomDataFetcherFactoryProvider(
private val objectMapper: ObjectMapper
) : SimpleKotlinDataFetcherFactoryProvider(objectMapper) {
override fun functionDataFetcherFactory(target: Any?, kFunction: KFunction<*>): DataFetcherFactory<Any> = DataFetcherFactory<Any> {
target = target,
fn = kFunction,
objectMapper = objectMapper)

With the above you can then create your schema as follows:

class ReactorQuery {
fun asynchronouslyDo(): Mono<Int> = Mono.just(1)
val configWithReactorMonoMonad = SchemaGeneratorConfig(
supportedPackages = listOf("myPackage"),
hooks = MonadHooks(),
dataFetcherFactoryProvider = CustomDataFetcherFactoryProvider())
toSchema(queries = listOf(TopLevelObject(ReactorQuery())), config = configWithReactorMonoMonad)

This will produce

type Query {
asynchronouslyDo: Int

You can find additional example on how to configure the hooks in our unit tests and example app.

Last updated on by jgorman-exp