springkotlinspring-webfluxkotlin-coroutinesgraphql-kotlin

Spring Kotlin RouterFunction endpoints with coroutines coRouter returns 404


I'm trying to set up the Spring Kotlin server with graphql-kotlin for the graphql, but it seems configuring the coRouter doesn't work (all routes return 404), which prevents the graphql to create all the routes.

I've tried changing the Spring version, graphql-kotlin version and adding/removing multiple libraries.

I'd like the coRouter to work. To test the coRouter I wrote this simple configuration to return string.

I'd appreciate any help

IndexController.kotlin

package com.project.client.general
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.reactive.function.server.bodyValueAndAwait
import org.springframework.web.reactive.function.server.coRouter


@Configuration
class IndexConfig {

    @Bean
    fun mainRouter() = coRouter {
        "/" {
            ok().bodyValueAndAwait(getIndex().trimIndent())
        }
    }

    suspend fun getIndex(): String {
        return  "Welcome to the API!"
    }
}

build.gradle.kts

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
//    id("org.springframework.boot") version "3.0.3"
//    id("io.spring.dependency-management") version "1.1.0"
//    kotlin("jvm") version "1.7.22"
//    kotlin("plugin.spring") version "1.7.22"
//    kotlin("plugin.jpa") version "1.7.22"
    id("org.springframework.boot") version "2.7.5"
    id("io.spring.dependency-management") version "1.0.15.RELEASE"
    kotlin("jvm") version "1.6.21"
    kotlin("plugin.spring") version "1.6.21"
    kotlin("plugin.jpa") version "1.6.21"
}

group = "com.project.client"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17

configurations {
    compileOnly {
        extendsFrom(configurations.annotationProcessor.get())
    }
}

repositories {
    mavenCentral()
}

extra["springShellVersion"] = "3.0.0"

dependencies {
//    Spring
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.hibernate.validator:hibernate-validator:8.0.0.Final")
    developmentOnly("org.springframework.boot:spring-boot-devtools")
    implementation("org.springframework.boot:spring-boot-starter-validation")
    testImplementation("org.springframework.boot:spring-boot-starter-test")

//    Webflux
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    testImplementation("io.projectreactor:reactor-test")
    implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
    implementation("io.projectreactor.addons:reactor-extra")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")

//    Jpa + db
    implementation("org.flywaydb:flyway-core")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
//    implementation("org.springframework.data:spring-data-r2dbc")
//    implementation("io.r2dbc:r2dbc-postgresql")
    runtimeOnly("org.postgresql:postgresql")

//    GraphQL
    implementation("com.expediagroup:graphql-kotlin-spring-server:7.0.0-alpha.3")
    implementation("com.expediagroup:graphql-kotlin-hooks-provider:7.0.0-alpha.3")
    implementation ("com.graphql-java:graphql-java-extended-scalars:20.0")

//    Security
//    implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
//    implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
//    implementation("org.springframework.boot:spring-boot-starter-security")
//    testImplementation("org.springframework.security:spring-security-test")
//    implementation("com.auth0:auth0-spring-security-api:1.5.3")
//    implementation("com.auth0:auth0:2.0.0")

    implementation("org.springframework.boot:spring-boot-starter-quartz")

//    Email
    implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
//    implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6")
//    implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity5")
    implementation("org.springframework.boot:spring-boot-starter-mail")

//    Kotlin
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

    compileOnly("org.projectlombok:lombok")
    annotationProcessor("org.projectlombok:lombok")

//    Logging
    runtimeOnly("io.micrometer:micrometer-registry-datadog")
    implementation("io.github.oshai:kotlin-logging-jvm:4.0.0-beta-22")

//    compileOnly(project(":shared"))
}

dependencyManagement {
    imports {
        mavenBom("org.springframework.shell:spring-shell-dependencies:${property("springShellVersion")}")
    }
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "17"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

SpringApplication.kotlin

package com.project.client

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.domain.EntityScan
import org.springframework.boot.runApplication
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
import org.springframework.scheduling.annotation.EnableAsync
import org.springframework.scheduling.annotation.EnableScheduling

@SpringBootApplication
@EntityScan
@EnableJpaRepositories
@EnableScheduling
@EnableAsync
class ClientBackendApplication {
    companion object {
        var context: ConfigurableApplicationContext? = null
    }
}


fun main(args: Array<String>) {
    ClientBackendApplication.context = runApplication<ClientBackendApplication>(*args)
}

Solution

  • For people struggling with this issue in the future, the culprit was the tomcat server. Only one of the org.springframework.boot:spring-boot-starter-web or org.springframework.boot:spring-boot-starter-webflux can be used at the same time. So disable spring-boot-starter-web :)