kotlinopenapijavalin

Encountering "no api definition provided" Error with Javalin OpenAPI Annotations


I'm working on a Javalin project where I'm trying to document my REST API using OpenAPI annotations. Despite annotating my controller method with @OpenApi, I'm encountering a "no api definition provided" error when trying to access the generated documentation via Swagger UI or ReDoc.

Here's the relevant portion of my Kotlin code where I set up Javalin, configure the OpenAPI plugin, and define a simple UserController:

package org.example

import io.javalin.Javalin
import io.javalin.apibuilder.ApiBuilder.*
import io.javalin.http.Context
import io.javalin.openapi.*
import io.javalin.openapi.plugin.OpenApiPlugin
import io.javalin.openapi.plugin.redoc.ReDocPlugin
import io.javalin.openapi.plugin.swagger.SwaggerPlugin
import java.util.concurrent.atomic.AtomicInteger

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

object UserService {

    private val users = hashMapOf(
        0 to User(id = 0, name = "Alice", email = "alice@alice.kt"),
        1 to User(id = 1, name = "Bob", email = "bob@bob.kt"),
        2 to User(id = 2, name = "Carol", email = "carol@carol.kt"),
        3 to User(id = 3, name = "Dave", email = "dave@dave.kt")
    )

    private var lastId: AtomicInteger = AtomicInteger(users.size - 1)

    fun getAll() = users.values
}

fun getConfiguredOpenApiPlugin(): OpenApiPlugin {
    return OpenApiPlugin { pluginConfig ->
        pluginConfig.withDefinitionConfiguration { _, definition ->
            definition.withInfo { info: OpenApiInfo ->
                info.title = "sentinel"
                info.version = "1.0.0"
                info.description = "sentinel"
            }
        }
        pluginConfig.documentationPath = "/swagger-docs"
    }
}

object UserController {
    @OpenApi(
        summary = "Get all users",
        operationId = "getAllUsers",
        tags = ["User"],
        responses = [OpenApiResponse("200", [OpenApiContent(Array<User>::class)])],
        path = "/users",
        methods = [HttpMethod.GET]
    )
    fun getAll(ctx: Context) {
        ctx.json(UserService.getAll())
    }
}
fun main() {

    Javalin.create { config ->
        config.registerPlugin(OpenApiPlugin { pluginConfig ->
            pluginConfig.withDefinitionConfiguration { version, definition ->
                definition.withInfo { info: OpenApiInfo ->
                    info.title = "Javalin OpenAPI example"
                }
            }
        })
        config.registerPlugin(SwaggerPlugin())
        config.registerPlugin(ReDocPlugin())
        config.router.apiBuilder {
            path("users") {
                get(UserController::getAll);
            }
        }
    }.start(7001)

    println("Check out ReDoc docs at http://localhost:7001/redoc")
    println("Check out Swagger UI docs at http://localhost:7001/swagger")

}

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>untitled</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>11</java.version>
        <kotlin.version>1.9.22</kotlin.version>
        <kotlin.code.style>official</kotlin.code.style>
        <javalin.version>6.1.0</javalin.version>
    </properties>

    <dependencies>
        <!-- Kotlin -->
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-reflect</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib-common</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-test</artifactId>
            <version>${kotlin.version}</version>
            <scope>test</scope>
        </dependency>

        <!-- Javalin -->
        <dependency>
            <groupId>io.javalin</groupId>
            <artifactId>javalin-bundle</artifactId>
            <version>${javalin.version}</version>
        </dependency>

        <dependency>
            <groupId>io.javalin.community.openapi</groupId>
            <artifactId>javalin-openapi-plugin</artifactId>
            <version>${javalin.version}</version>
        </dependency>
        <dependency>
            <groupId>io.javalin.community.openapi</groupId>
            <artifactId>javalin-swagger-plugin</artifactId>
            <version>${javalin.version}</version>
        </dependency>
        <dependency>
            <groupId>io.javalin.community.openapi</groupId>
            <artifactId>javalin-redoc-plugin</artifactId>
            <version>${javalin.version}</version>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-plugin</artifactId>
                <version>${kotlin.version}</version>
                <configuration>
                    <jvmTarget>11</jvmTarget>
                </configuration>
                <executions>
                    <execution>
                        <id>compile</id>
                        <phase>process-sources</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>test-compile</id>
                        <phase>test-compile</phase>
                        <goals>
                            <goal>test-compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.10.1</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                    <annotationProcessorPaths>
                        <annotationProcessorPath>
                            <groupId>io.javalin.community.openapi</groupId>
                            <artifactId>openapi-annotation-processor</artifactId>
                            <version>6.0.0-SNAPSHOT</version>
                        </annotationProcessorPath>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.openapitools</groupId>
                <artifactId>openapi-generator-maven-plugin</artifactId>
                <version>5.1.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <generatorName>kotlin</generatorName>
                            <inputSpec>${project.basedir}/src/main/resources/openapi.json</inputSpec>
                            <configOptions>
                                <sourceFolder>src/gen/java/main</sourceFolder>
                            </configOptions>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Here is the error:

enter image description here


Solution

  • I faced a similar issue in Javalin 5. What a did was extract the open api config to an auxiliary variable and then register the plugin.

    For example:

    
    Javalin.create { config -> 
    
    ... // other code/configs
    
        val openApiConfig =
            OpenApiPluginConfiguration()
                .withDocumentationPath("/openapi")
                .withDefinitionConfiguration { version, openApiDefinition ->
                    openApiDefinition
                        .withOpenApiInfo { openApiInfo ->
                            openApiInfo.description = "App description goes right here"
                            openApiInfo.contact =
                                OpenApiContact().apply {
                                    name = "API Support"
                                    url = "https://www.example.com/support"
                                    email = "support@example.com"
                                }
                        }
                        .withServer { openApiServer ->
                            openApiServer.description
                            openApiServer.url
                            openApiServer.addVariable(
                                "port",
                                OpenApiServerVariable().apply {
                                    description = "Server's port"
                                    default = "8080"
                                },
                            )
                        }
                }
    
        config.plugins.register(OpenApiPlugin(openApiConfig))
        config.plugins.register(SwaggerPlugin())
    ...
    }
    

    Also, in my case, I had to import related dependencies for Javalin 5:

        kapt "io.javalin.community.openapi:openapi-annotation-processor:$openapi_version"
        implementation "io.javalin.community.openapi:javalin-openapi-plugin:$openapi_version"
        implementation "io.javalin.community.openapi:javalin-swagger-plugin:$openapi_version"
    

    Hope you solve it!