Kora OpenAPI Generator — HTTP Server
Kora sub-skill — obey the kora-v1 meta rules on every task: R0 ensure
.kora-agent/docs+examples are cloned · R1 read this sub-skill before writing code · R2 Kora APIs only — no Spring/Micronaut/Quarkus, no invented annotations or config keys · R3 journal any incorrect Kora usage. Add comments/Javadoc only if asked.
Generate a type-safe Kora HTTP server from an OpenAPI 3.x contract. The kora
generator emits the transport layer (controller, response wrappers, models); you
implement one generated *ApiDelegate interface with @Component and return the
generated sealed *ApiResponses records. The generated controller registers
routes automatically — never write @HttpController/@HttpRoute by hand for a
generated API, and never edit files under build/generated/.
All Kora artifacts inherit their version from the kora-parent BOM
(ru.tinkoff.kora:kora-parent, e.g. 1.2.19 in the example apps) — never pin a
version on an individual ru.tinkoff.kora:* dependency. The OpenAPI plugin
org.openapi.generator is pinned to 7.14.0; other versions are not guaranteed
to be code-compatible.
Quick Start
1. Dependencies
===! ":fontawesome-brands-java: Java"
```groovy title="build.gradle"
import org.openapitools.generator.gradle.plugin.tasks.GenerateTask
buildscript {
dependencies {
classpath("ru.tinkoff.kora:openapi-generator:$koraVersion")
}
}
plugins {
id "java"
id "application"
id "org.openapi.generator" version "7.14.0"
}
configurations {
koraBom
annotationProcessor.extendsFrom(koraBom)
implementation.extendsFrom(koraBom)
}
dependencies {
koraBom platform("ru.tinkoff.kora:kora-parent:$koraVersion")
annotationProcessor "ru.tinkoff.kora:annotation-processors"
implementation "ru.tinkoff.kora:http-server-undertow"
implementation "ru.tinkoff.kora:json-module"
implementation "ru.tinkoff.kora:config-hocon"
implementation "ru.tinkoff.kora:logging-logback"
implementation "ru.tinkoff.kora:openapi-management" // serves /openapi + /swagger-ui
implementation "ru.tinkoff.kora:validation-module" // needed for enableServerValidation
}
```
=== ":simple-kotlin: Kotlin"
```kotlin title="build.gradle.kts"
import org.openapitools.generator.gradle.plugin.tasks.GenerateTask
buildscript {
dependencies {
classpath("ru.tinkoff.kora:openapi-generator:$koraVersion")
}
}
plugins {
kotlin("jvm") version "1.9.24"
id("application")
id("org.openapi.generator") version "7.14.0"
id("com.google.devtools.ksp") version "1.9.24-1.0.20"
}
configurations {
koraBom
ksp.extendsFrom(koraBom)
implementation.extendsFrom(koraBom)
}
dependencies {
koraBom(platform("ru.tinkoff.kora:kora-parent:$koraVersion"))
ksp("ru.tinkoff.kora:symbol-processors")
implementation("ru.tinkoff.kora:http-server-undertow")
implementation("ru.tinkoff.kora:json-module")
implementation("ru.tinkoff.kora:config-yaml")
implementation("ru.tinkoff.kora:logging-logback")
implementation("ru.tinkoff.kora:openapi-management")
implementation("ru.tinkoff.kora:validation-module")
}
```
2. Generation task
===! ":fontawesome-brands-java: Java"
```groovy title="build.gradle"
def openApiGenerateHttpServer = tasks.register("openApiGenerateHttpServer", GenerateTask) {
generatorName = "kora"
group = "openapi tools"
inputSpec = "$projectDir/src/main/resources/openapi/user-api.yaml"
outputDir = "$buildDir/generated/user-api-server" // unique per API
def corePackage = "com.example.userapi"
apiPackage = "${corePackage}.api"
modelPackage = "${corePackage}.model"
invokerPackage = "${corePackage}.invoker"
openapiNormalizer = [DISABLE_ALL: "true"] // keeps oneOf/allOf intact
configOptions = [
mode : "java-server",
enableServerValidation: "true",
]
}
sourceSets.main { java.srcDirs += openApiGenerateHttpServer.get().outputDir }
compileJava.dependsOn openApiGenerateHttpServer
```
=== ":simple-kotlin: Kotlin"
```kotlin title="build.gradle.kts"
val openApiGenerateHttpServer = tasks.register<GenerateTask>("openApiGenerateHttpServer") {
generatorName = "kora"
group = "openapi tools"
inputSpec = "$projectDir/src/main/resources/openapi/user-api.yaml"
outputDir = "$buildDir/generated/user-api-server" // unique per API
val corePackage = "com.example.userapi"
apiPackage = "$corePackage.api"
modelPackage = "$corePackage.model"
invokerPackage = "$corePackage.invoker"
openapiNormalizer = mapOf("DISABLE_ALL" to "true")
configOptions = mapOf(
"mode" to "kotlin-server",
"enableServerValidation" to "true",
)
}
kotlin.sourceSets.main { kotlin.srcDir(openApiGenerateHttpServer.get().outputDir) }
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KspTask> {
dependsOn(openApiGenerateHttpServer)
}
```
3. Plug the modules into @KoraApp
@KoraApp
public interface Application extends
HoconConfigModule,
LogbackModule,
JsonModule,
ValidationModule, // only if enableServerValidation = true
UndertowHttpServerModule,
OpenApiManagementModule { // exposes /openapi + /swagger-ui
static void main(String[] args) {
KoraApplication.run(ApplicationGraph::graph);
}
}
KoraApplication is ru.tinkoff.kora.application.graph.KoraApplication.
4. Implement the generated delegate
The generator emits UsersApiDelegate (one method per operationId) and
UsersApiResponses (one sealed interface per operation, one record per declared
status). Implement the delegate, returning the matching response record.
For advanced codegen options (requestInDelegateParams, oneOf handling, enableServerValidation),
see Advanced Codegen Options.
===! ":fontawesome-brands-java: Java"
```java
package com.example.userapi.controller;
import ru.tinkoff.kora.common.Component;
import com.example.userapi.api.UsersApiDelegate;
import com.example.userapi.api.UsersApiResponses;
import com.example.userapi.model.ErrorResponseTO;
@Component
public final class UserApiDelegateImpl implements UsersApiDelegate {
private final UserService userService;
public UserApiDelegateImpl(UserService userService) {
this.userService = userService;
}
@Override
public UsersApiResponses.GetUserApiResponse getUser(String userId) {
return userService.findById(userId)
.<UsersApiResponses.GetUserApiResponse>map(user ->
new UsersApiResponses.GetUserApiResponse.GetUser200ApiResponse(toTO(user)))
.orElseGet(() ->
new UsersApiResponses.GetUserApiResponse.GetUser404ApiResponse(
new ErrorResponseTO("User not found: " + userId)));
}
}
```
=== ":simple-kotlin: Kotlin"
```kotlin
package com.example.userapi.controller
import ru.tinkoff.kora.common.Component
import com.example.userapi.api.UsersApiDelegate
import com.example.userapi.api.UsersApiResponses
import com.example.userapi.model.ErrorResponseTO
@Component
class UserApiDelegateImpl(
private val userService: UserService
) : UsersApiDelegate {
override fun getUser(userId: String): UsersApiResponses.GetUserApiResponse =
userService.findById(userId)
?.let { UsersApiResponses.GetUserApiResponse.GetUser200ApiResponse(it.toTO()) }
?: UsersApiResponses.GetUserApiResponse.GetUser404ApiResponse(
ErrorResponseTO("User not found: $userId"))
}
```
5. Build and run
./gradlew clean classes # runs the generator, then compiles
./gradlew run
Server modes
Set via configOptions.mode. The delegate method's return type follows the mode.
| Mode | Delegate return type | Notes |
|---|---|---|
java-server |
*ApiResponses.*ApiResponse |
Synchronous (recommended start) |
java-async-server |
CompletionStage<*ApiResponse> |
Non-blocking |
java-reactive-server |
Mono<*ApiResponse> |
Add io.projectreactor:reactor-core yourself |
kotlin-server |
*ApiResponses.*ApiResponse |
Synchronous Kotlin |
kotlin-suspend-server |
suspend fun ... : *ApiResponse |
Coroutine-based |
Core rules
- Implement
*ApiDelegateonly. It is the single implementation point. The*ApiControllerand*ApiResponsesare generated — do not touch them. - Return the generated sealed
*ApiResponsesrecord, never a raw DTO. There is noResponseEntityin Kora. - A response record exists only for a status declared in the contract. If you
need a
GetUser500ApiResponse, declare"500"under that operation'sresponses. @Componentmakes the delegate discoverable by the compile-time graph. Keep business logic in services; the delegate maps between generated transport models and your internal DTOs.openapiNormalizer = [DISABLE_ALL: "true"]when usingoneOf/allOf— since plugin 7.0.0 theSIMPLIFY_ONEOF_ANYOFrule rewrites polymorphic schemas.oneOfwithout discriminator collapses to empty record in 7.14.0 — flatten to single schema with nullable fields, or add explicitdiscriminator(see Advanced Codegen).
Common pitfalls
| Symptom | Fix |
|---|---|
| Delegate not discovered ("required dependency not found") | Add @Component; confirm it implements the generated *ApiDelegate from your apiPackage. |
| Wrong return type / compile error | Return *ApiResponses.<Op><Status>ApiResponse, not a DTO or ResponseEntity. |
| Generated classes missing | Register outputDir in sourceSets.main and add compileJava.dependsOn (Java) / KspTask dependsOn (Kotlin). |
| Discriminator collapsed to a single type | Set openapiNormalizer = [DISABLE_ALL: "true"]. |
| Validation annotations absent | Set enableServerValidation: "true" and add validation-module + ValidationModule. |
| Two tasks overwrite each other | Give each generator task a unique outputDir. |
/swagger-ui missing |
Add OpenApiManagementModule and enable openapi.management in config. |
oneOf without discriminator generates empty record |
Kora generator 7.14.0 bug — flatten to single schema with nullable fields, or add discriminator (see Advanced Codegen) |
Need raw HttpServerRequest in delegate |
Set requestInDelegateParams: "true" (see Advanced Codegen) |
References
| Document | Covers |
|---|---|
| Codegen Reference | Full configOptions table, modes, normalizer |
| Delegates Reference | *ApiDelegate shapes, sync/async/reactive/suspend, requestInDelegateParams, delegateMethodBodyMode |
| Response Reference | Sealed *ApiResponses records, headers, 204, status selection |
| Controllers Reference | Generated controller, prefixPath, interceptors, validation interceptor |
| Models Reference | Generated records, enums, dates, JsonNullable, discriminators |
| Validation Reference | Kora validation annotations from schema constraints |
| Authorization Reference | securitySchemes → HttpServerPrincipalExtractor + ApiSecurity tags |
Source of truth: openapi-codegen.md, guides openapi-http-server.md and openapi-http-server-advanced.md.
Assets
| Asset | Purpose |
|---|---|
assets/build.gradle.server.template / assets/build.gradle.kts.server.template |
Annotated build config for server generation |
assets/Application.server.java.template / .kt.template |
@KoraApp with OpenAPI management + validation |
assets/PetApiDelegate.server.java.template / .kt.template |
Delegate implementation example |
assets/openapi-spec.yaml.template |
Full OpenAPI 3.x spec with CRUD + discriminators |
assets/templates/ |
Reusable spec snippets, delegate and response patterns |
scripts/validate_openapi.py |
Pre-generation spec sanity check |