OpenAPI codegen
This module generates Kora code from an OpenAPI contract using OpenAPI Generator.
From a single API description, it can create declarative HTTP server handlers or declarative HTTP clients,
as well as request and response models, mappers, authorization handling, and additional annotations.
This approach is useful when OpenAPI is the source of truth for the transport contract and application code must follow it automatically.
For a step-by-step walkthrough before the reference documentation, see OpenAPI HTTP Server, Advanced OpenAPI HTTP Server, and OpenAPI HTTP Client.
Dependency¶
Generator dependency in build.gradle:
Plugin dependency in build.gradle:
Other plugin versions are not guaranteed to work because the OpenAPI Generator API can be incompatible at code level.
Dependency in build.gradle.kts:
Plugin dependency in build.gradle.kts:
Other plugin versions are not guaranteed to work because the OpenAPI Generator API can be incompatible at code level.
Generated code also requires the HTTP server or HTTP client module, depending on the selected generation mode.
Configuration¶
Configure the OpenAPI Generator plugin parameters:
Gradleplugin parameters are described in the plugin documentation.- The
configOptionsplugin parameter is described in the configuration documentation. - The
openapiNormalizerplugin parameter is described in the customization documentation.
Common OpenAPI Generator Options¶
In addition to Kora-specific configOptions, GenerateTask accepts common OpenAPI Generator parameters.
They define where to read the contract from, where to put generated files, which packages to use, and how to preprocess the OpenAPI description.
For Kora projects, these parameters are usually set explicitly because generated code is then added to normal project compilation.
| Parameter | Description |
|---|---|
generatorName |
Generator name (required, no default). Always set it to kora for Kora. |
inputSpec |
Path to the OpenAPI file (required, no default). Usually this is a file under src/main/resources/openapi, for example $projectDir/src/main/resources/openapi/openapi.yaml. |
outputDir |
Directory for generated files (not specified by default, optional). In Kora projects, this is usually a directory under build, for example $buildDir/generated/openapi, and it is added to the main source set. |
apiPackage |
Package for generated API interfaces, controllers, delegate classes, and mappers (default: org.openapitools.api). It is recommended to set it explicitly, for example ru.tinkoff.kora.example.openapi.api. |
modelPackage |
Package for models generated from OpenAPI schemas (default: org.openapitools.model). It is recommended to set it explicitly, for example ru.tinkoff.kora.example.openapi.model. |
invokerPackage |
Auxiliary generator package (default: org.openapitools.api). It is recommended to set it explicitly next to apiPackage and modelPackage, for example ru.tinkoff.kora.example.openapi.invoker. |
configOptions |
Generator-specific parameters (default: {}). For Kora, this is where mode, clientConfigPrefix, enableServerValidation, interceptors, and the other parameters described below are set. |
globalProperties |
Limits which entities are generated (default: {}). Useful when you need to generate only apis, only models, or specific models and operations. Use carefully: normal Kora clients and servers usually need API classes, models, and mappers together. |
openapiNormalizer |
Preprocesses the OpenAPI contract before generation (default: {}). Often used to disable standard transformations with DISABLE_ALL, generate only selected operations with FILTER, or control rules such as SIMPLIFY_ONEOF_ANYOF. |
importMappings |
Maps a schema name to an existing class (default: {}). Useful when a model is written manually or comes from another module, for example Money: "com.example.Money". |
typeMappings |
Maps an OpenAPI Generator type to a language type (default: {}). Used for targeted type replacement, for example replacing OffsetDateTime with a project-specific time type. |
schemaMappings |
Maps an OpenAPI schema to an external type without generating the model (default: {}). Similar to importMappings, but configured at schema level and useful for reusing shared DTOs. |
skipValidateSpec |
Skips OpenAPI contract validation before generation (default: false). In normal builds it is better to keep validation enabled; use true only temporarily for external contracts that cannot be fixed quickly. |
cleanupOutput |
Cleans outputDir before generation (default: false). Useful when the contract changes often and files from removed operations or models must disappear. Do not point outputDir to a directory with handwritten code. |
Example with common options:
def openApiGenerateHttpClient = tasks.register("openApiGenerateHttpClient", GenerateTask) {
generatorName = "kora"
inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml"
outputDir = "$buildDir/generated/openapi/client"
def corePackage = "ru.tinkoff.kora.example.openapi"
apiPackage = "${corePackage}.api"
modelPackage = "${corePackage}.model"
invokerPackage = "${corePackage}.invoker"
skipValidateSpec = false
cleanupOutput = true
openapiNormalizer = [
DISABLE_ALL: "true",
FILTER: "tag:public|billing"
]
configOptions = [
mode: "java-client",
clientConfigPrefix: "httpClient.billing",
filterWithModels: "true"
]
}
val openApiGenerateHttpClient = tasks.register<GenerateTask>("openApiGenerateHttpClient") {
generatorName = "kora"
inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml"
outputDir = "$buildDir/generated/openapi/client"
val corePackage = "ru.tinkoff.kora.example.openapi"
apiPackage = "${corePackage}.api"
modelPackage = "${corePackage}.model"
invokerPackage = "${corePackage}.invoker"
skipValidateSpec = false
cleanupOutput = true
openapiNormalizer = mapOf(
"DISABLE_ALL" to "true",
"FILTER" to "tag:public|billing"
)
configOptions = mapOf(
"mode" to "kotlin-client",
"clientConfigPrefix" to "httpClient.billing",
"filterWithModels" to "true"
)
}
Use globalProperties only for narrow generation tasks, for example when extracting a few models into an intermediate module:
Useful openapiNormalizer Rules¶
openapiNormalizer changes the input OpenAPI contract before generation. It is not a Kora parameter, but a general OpenAPI Generator mechanism.
For Kora, it is especially useful when one large contract is used by several applications or when the contract contains ambiguous shapes for code generation.
| Rule | Description |
|---|---|
DISABLE_ALL |
Disables standard normalization rules (default: false). Starting with OpenAPI Generator 7, some rules are enabled by default, so predictable generation often starts with DISABLE_ALL: "true" and then enables only the needed rules explicitly. |
FILTER |
Keeps only selected operations for generation (not specified by default, optional). Supports one filter at a time: operationId:name1\|name2, method:get\|post, or tag:public\|billing. Operations that do not match are marked as x-internal: true and are not generated. |
KEEP_ONLY_FIRST_TAG_IN_OPERATION |
Keeps only the first tag on an operation (default: false). Useful when operations have several tags and are split into several API classes differently from what you expect. |
SET_TAGS_FOR_ALL_OPERATIONS |
Replaces tags on all operations with one provided value (not specified by default, optional). Useful when you want to force one generated API class. |
SET_TAGS_TO_OPERATIONID |
Sets an operation tag to operationId, or to default when operationId is empty (default: false). Useful for contracts without usable tags when predictable operation grouping is needed. |
SET_TAGS_TO_VENDOR_EXTENSION |
Reads operation tags from the specified extension, for example x-tags (not specified by default, optional). Useful when an external contract cannot be changed but already has custom operation grouping. |
FIX_DUPLICATED_OPERATIONID |
Adds a numeric suffix to duplicated operationId values (default: false). It is better to fix the contract, but this rule helps generate code for an external description temporarily. |
SET_BEARER_AUTH_FOR_NAME |
Converts the specified security scheme to bearerAuth (not specified by default, optional). Useful for external contracts where a bearer token is described in a non-standard way but should be handled as a normal bearer scheme in the application. |
REF_AS_PARENT_IN_ALLOF |
Marks a $ref inside allOf as a parent schema with x-parent: true (default: false). Can help contracts that model inheritance through allOf. |
SIMPLIFY_ONEOF_ANYOF |
Simplifies some oneOf/anyOf constructs, for example by moving a null variant to nullable: true and removing single wrappers (enabled by default in OpenAPI Generator 7 unless DISABLE_ALL is set). For Kora, this can change generated model shapes, so enable it deliberately. |
SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING |
Simplifies anyOf made from string and a string enum to string (default: false). This can help with contracts where the enum restriction is not important for code. |
SIMPLIFY_BOOLEAN_ENUM |
Converts a boolean enum to a plain boolean (enabled by default in OpenAPI Generator 7 unless DISABLE_ALL is set). |
REFACTOR_ALLOF_WITH_PROPERTIES_ONLY |
Moves properties from a schema that has both allOf and properties into a separate schema inside allOf (enabled by default in OpenAPI Generator 7 unless DISABLE_ALL is set). This can help inheritance, but strict contracts should be checked after generation. |
NORMALIZE_31SPEC |
Normalizes some OpenAPI 3.1 constructs into a form better understood by the generator (default: false). Useful for 3.1 contracts when generation fails on newer schema forms. |
REMOVE_X_INTERNAL |
Removes x-internal: true from operations and models (default: false). Use only when the contract already contains x-internal, but a specific generation task must force such operations back in. |
SET_CONTAINER_TO_NULLABLE |
Marks container types array, set, or map as nullable (not specified by default, optional). Use only when an external contract systematically misses nullable on such fields. |
SET_PRIMITIVE_TYPES_TO_NULLABLE |
Marks primitive types string, integer, number, or boolean as nullable (not specified by default, optional). This significantly changes model signatures, so apply it only to problematic external contracts. |
Example of generating only the public part of a contract:
FILTER excludes only operations by itself. If unused models should also be removed after filtering, enable the Kora filterWithModels parameter.
For more complex selection, usually create separate generation tasks with different FILTER values, for example one with tag:billing and another with operationId:createUser|getUser.
Example of normalizing tags for a contract without convenient grouping:
Common JSON and Model Options¶
Kora also supports several configOptions that control JSON mappers and common model generation.
They do not depend on whether a client or a server is generated.
| Parameter | Description |
|---|---|
jsonAnnotation |
Annotation tag used to inject JSON mappers into generated request and response mappers (default: ru.tinkoff.kora.json.common.annotation.Json). |
objectType |
Type for type: object schemas without a more precise description. Java uses java.lang.Object by default, and Kotlin uses kotlin.Any. For example, set it to com.fasterxml.jackson.databind.JsonNode if the application wants to handle arbitrary JSON as a tree. |
disableHtmlEscaping |
Disables HTML character escaping in JSON strings (default: false). Usually the default value is kept. |
ignoreAnyOfInEnum |
Ignores anyOf when generating enums (default: false). Can help with contracts where an enum is described through mixed anyOf constructs. |
discriminatorCaseSensitive |
Controls case sensitivity of the discriminator value lookup for polymorphic (oneOf) models with a discriminator (default: true). Set to false when incoming discriminator values may differ in case from the schema definition. |
additionalModelTypeAnnotations |
Additional annotations on model types (not specified by default, optional). Several annotations are separated by ;, for example @Deprecated;@MyAnnotation. |
additionalEnumTypeAnnotations |
Additional annotations on enum types (not specified by default, optional). Several annotations are separated by ;. |
Example:
Multiple Generation Tasks¶
Several GenerateTask tasks can be registered in one module, for example to generate two independent contracts,
or to generate a client for one contract and a server for another. Each task writes into the same outputDir and is added to the same source set,
so the only requirement is that generated packages do not collide. Give every task its own apiPackage/modelPackage/invokerPackage.
def openApiGeneratePetV2 = tasks.register("openApiGeneratePetV2", GenerateTask) {
generatorName = "kora"
inputSpec = "$projectDir/src/main/resources/openapi/petstoreV2.yaml"
outputDir = "$buildDir/generated/openapi"
def corePackage = "ru.tinkoff.kora.example.openapi.petV2" //(1)!
apiPackage = "${corePackage}.api"
modelPackage = "${corePackage}.model"
invokerPackage = "${corePackage}.invoker"
configOptions = [mode: "java-client", clientConfigPrefix: "httpClient.petV2"]
}
sourceSets.main { java.srcDirs += openApiGeneratePetV2.get().outputDir }
compileJava.dependsOn openApiGeneratePetV2
def openApiGeneratePetV3 = tasks.register("openApiGeneratePetV3", GenerateTask) {
generatorName = "kora"
inputSpec = "$projectDir/src/main/resources/openapi/petstoreV3.yaml"
outputDir = "$buildDir/generated/openapi"
def corePackage = "ru.tinkoff.kora.example.openapi.petV3" //(2)!
apiPackage = "${corePackage}.api"
modelPackage = "${corePackage}.model"
invokerPackage = "${corePackage}.invoker"
configOptions = [mode: "java-reactive-client", clientConfigPrefix: "httpClient.petV3"]
}
sourceSets.main { java.srcDirs += openApiGeneratePetV3.get().outputDir }
compileJava.dependsOn openApiGeneratePetV3
- Isolated package for the first contract
- Different package for the second contract, so class names cannot clash
val openApiGeneratePetV2 = tasks.register<GenerateTask>("openApiGeneratePetV2") {
generatorName = "kora"
inputSpec = "$projectDir/src/main/resources/openapi/petstoreV2.yaml"
outputDir = "$buildDir/generated/openapi"
val corePackage = "ru.tinkoff.kora.example.openapi.petV2" //(1)!
apiPackage = "${corePackage}.api"
modelPackage = "${corePackage}.model"
invokerPackage = "${corePackage}.invoker"
configOptions = mapOf("mode" to "kotlin-client", "clientConfigPrefix" to "httpClient.petV2")
}
kotlin.sourceSets.main { kotlin.srcDir(openApiGeneratePetV2.get().outputDir) }
tasks.withType<KspTask> { dependsOn(openApiGeneratePetV2) }
val openApiGeneratePetV3 = tasks.register<GenerateTask>("openApiGeneratePetV3") {
generatorName = "kora"
inputSpec = "$projectDir/src/main/resources/openapi/petstoreV3.yaml"
outputDir = "$buildDir/generated/openapi"
val corePackage = "ru.tinkoff.kora.example.openapi.petV3" //(2)!
apiPackage = "${corePackage}.api"
modelPackage = "${corePackage}.model"
invokerPackage = "${corePackage}.invoker"
configOptions = mapOf("mode" to "kotlin-suspend-client", "clientConfigPrefix" to "httpClient.petV3")
}
kotlin.sourceSets.main { kotlin.srcDir(openApiGeneratePetV3.get().outputDir) }
tasks.withType<KspTask> { dependsOn(openApiGeneratePetV3) }
- Isolated package for the first contract
- Different package for the second contract, so class names cannot clash
Client¶
A minimal plugin configuration for creating a declarative HTTP client:
For clients, configOptions.mode supports java-client, java-async-client, and java-reactive-client.
Other client parameters are described below in the authorization, interceptors, tags, models, and implicit headers sections.
def openApiGenerateHttpClient = tasks.register("openApiGenerateHttpClient", GenerateTask) {
generatorName = "kora"
group = "openapi tools"
inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml" //(1)!
outputDir = "$buildDir/generated/openapi" //(2)!
def corePackage = "ru.tinkoff.kora.example.openapi"
apiPackage = "${corePackage}.api" //(3)!
modelPackage = "${corePackage}.model" //(4)!
invokerPackage = "${corePackage}.invoker" //(5)!
openapiNormalizer = [
DISABLE_ALL: "true"
]
configOptions = [
mode: "java-client", //(6)!
clientConfigPrefix: "httpClient.myclient" //(7)!
]
}
sourceSets.main { java.srcDirs += openApiGenerateHttpClient.get().outputDir } //(8)!
compileJava.dependsOn openApiGenerateHttpClient //(9)!
- Path to the
OpenAPIfile used to create classes - Directory where generated files are created
- Package for delegates, controllers, and mappers
- Package for models and DTOs
- Auxiliary generator package
- Plugin mode
- Client configuration path prefix
- Register generated classes as project source code
- Make code compilation depend on HTTP client class generation: generate first, compile after
For clients, configOptions.mode supports kotlin-client and kotlin-suspend-client.
Other client parameters are described below in the authorization, interceptors, tags, models, and implicit headers sections.
val openApiGenerateHttpClient = tasks.register<GenerateTask>("openApiGenerateHttpClient") {
generatorName = "kora"
group = "openapi tools"
inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml" //(1)!
outputDir = "$buildDir/generated/openapi" //(2)!
val corePackage = "ru.tinkoff.kora.example.openapi"
apiPackage = "${corePackage}.api" //(3)!
modelPackage = "${corePackage}.model" //(4)!
invokerPackage = "${corePackage}.invoker" //(5)!
openapiNormalizer = mapOf(
"DISABLE_ALL" to "true"
)
configOptions = mapOf(
"mode" to "kotlin-client", //(6)!
"clientConfigPrefix" to "httpClient.myclient" //(7)!
)
}
kotlin.sourceSets.main { kotlin.srcDir(openApiGenerateHttpClient.get().outputDir) } //(8)!
tasks.withType<KspTask> { dependsOn(openApiGenerateHttpClient) } //(9)!
- Path to the
OpenAPIfile used to create classes - Directory where generated files are created
- Package for delegates, controllers, and mappers
- Package for models and DTOs
- Auxiliary generator package
- Plugin mode
- Client configuration path prefix
- Register generated classes as project source code
- Make code compilation depend on HTTP client class generation: generate first, compile after
After generation, the HTTP client is available for dependency injection through the generated interface.
Generated Client Usage¶
For every API tag, the generator produces an interface annotated with @HttpClient, named after the tag (for example PetApi).
It is injected into components like any other Kora client, without extra registration:
The generated client reads its configuration from the path given by clientConfigPrefix, followed by the generated interface name.
For clientConfigPrefix = "httpClient.petV2" and interface PetApi, the configuration block is httpClient.petV2.PetApi.
The full set of client options (url, requestTimeout, per-operation blocks, telemetry) is described in the HTTP client documentation:
httpClient.petV2.PetApi {
url = "https://localhost:8443" //(1)!
requestTimeout = "10s" //(2)!
getValuesConfig { //(3)!
requestTimeout = "20s"
}
telemetry.logging.enabled = true
}
- Base URL of the target service
- Default request timeout for all operations
- Per-operation override block, named after the
operationId(heregetValues)
httpClient:
petV2:
PetApi:
url: "https://localhost:8443" #(1)!
requestTimeout: "10s" #(2)!
getValuesConfig: #(3)!
requestTimeout: "20s"
telemetry:
logging:
enabled: true
- Base URL of the target service
- Default request timeout for all operations
- Per-operation override block, named after the
operationId(heregetValues)
The client method signatures depend on the selected mode:
| Mode | Return type example |
|---|---|
java-client |
PetApiResponses.GetPetByIdApiResponse (blocking value) |
java-async-client |
CompletionStage<PetApiResponses.GetPetByIdApiResponse> |
java-reactive-client |
Mono<PetApiResponses.GetPetByIdApiResponse> (requires reactor-core) |
kotlin-client |
PetApiResponses.GetPetByIdApiResponse (blocking value) |
kotlin-suspend-client |
suspend fun ...: PetApiResponses.GetPetByIdApiResponse |
Every method returns a sealed *ApiResponses envelope whose subtypes encode the HTTP status, the same way server delegates do.
Client Authorization¶
If the OpenAPI contract describes securitySchemes, the generator creates an ApiSecurity module with components for client authorization.
For apiKey and basic, configuration-reading components are generated. For bearer and oauth, a matching tagged HttpClientTokenProvider component is expected.
securityConfigPrefix sets a common authorization configuration prefix. If the prefix is not specified, the configuration path is the securitySchemes name.
If an operation has several authorization schemes, primaryAuth can be specified; otherwise the generator picks one of the schemes and logs a warning.
If authAllowMultiple is enabled, the generator creates a composite interceptor that applies several authorization schemes sequentially.
If authAsMethodArgument is enabled, authorization data is added to the client method signature instead of a generated interceptor.
apiKey and basic¶
For apiKey and basic schemes, the generator produces @DefaultComponent config readers and interceptors, so no beans are required — only configuration values.
The configuration path is securityConfigPrefix followed by the scheme name (or just the scheme name when securityConfigPrefix is not set).
An apiKey scheme reads a single string; a basic scheme reads a username/password object:
openapiAuth {
apiKeyAuth = "MyAuthApiKey" //(1)!
basicAuth { //(2)!
username = "user"
password = "password"
}
}
apiKeyschemeapiKeyAuth: value sent by the generatedApiKeyHttpClientInterceptorin the header/query/cookie declared by the schemebasicschemebasicAuth: credentials wrapped by the generatedBasicAuthHttpClientInterceptor
openapiAuth:
apiKeyAuth: "MyAuthApiKey" #(1)!
basicAuth: #(2)!
username: "user"
password: "password"
apiKeyschemeapiKeyAuth: value sent by the generatedApiKeyHttpClientInterceptorin the header/query/cookie declared by the schemebasicschemebasicAuth: credentials wrapped by the generatedBasicAuthHttpClientInterceptor
bearer and oauth¶
For bearer and oauth schemes, the generator expects an HttpClientTokenProvider component tagged with the generated ApiSecurity marker class
(for example ApiSecurity.BearerAuth). The generator wraps it in a BearerAuthHttpClientInterceptor automatically, so only the token provider must be supplied:
@Module
public interface ClientAuthModule {
@Tag(ApiSecurity.BearerAuth.class) //(1)!
default HttpClientTokenProvider bearerTokenProvider() {
return request -> CompletableFuture.completedFuture("my-token"); //(2)!
}
}
- Tag must match the generated marker class for the scheme
- Real implementations usually fetch or refresh the token here
@Module
interface ClientAuthModule {
@Tag(ApiSecurity.BearerAuth::class) //(1)!
fun bearerTokenProvider(): HttpClientTokenProvider {
return HttpClientTokenProvider { CompletableFuture.completedFuture("my-token") } //(2)!
}
}
- Tag must match the generated marker class for the scheme
- Real implementations usually fetch or refresh the token here
Multiple schemes¶
When an operation declares several security schemes, primaryAuth selects which one to apply; otherwise the generator picks one and logs a warning.
To apply several schemes on the same request, enable authAllowMultiple — the generator builds a composite interceptor that runs each scheme sequentially.
To pass the credentials explicitly per call instead of through an interceptor, enable authAsMethodArgument — the authorization value becomes a client method argument:
configOptions = [
mode: "java-client",
securityConfigPrefix: "openapiAuth",
primaryAuth: "apiKeyAuth", //(1)!
authAllowMultiple: "false", //(2)!
authAsMethodArgument: "false" //(3)!
]
- Scheme applied when an operation lists several
- Apply every declared scheme with a composite interceptor
- Add the auth value as a method argument instead of an interceptor
configOptions = mapOf(
"mode" to "kotlin-client",
"securityConfigPrefix" to "openapiAuth",
"primaryAuth" to "apiKeyAuth", //(1)!
"authAllowMultiple" to "false", //(2)!
"authAsMethodArgument" to "false" //(3)!
)
- Scheme applied when an operation lists several
- Apply every declared scheme with a composite interceptor
- Add the auth value as a method argument instead of an interceptor
Additional Annotations¶
The additionalContractAnnotations parameter adds annotations above generated client or server controller methods.
The value is a JSON object where the key is the API tag from the contract, or * for all operations, and the value is an array of objects with the annotation field.
Interceptors¶
Generated clients annotated with @HttpClient can also be annotated with interceptors.
The value is a JSON object where the key is an API tag from the contract and the value is an array of objects with type and tag fields.
Both fields can be specified together, or only one of them can be specified:
type- implementation class of a concrete interceptortag- interceptor tags, either a string or an array of strings
Set configOptions.interceptors:
Tags¶
Generated clients annotated with @HttpClient can receive httpClientTag and telemetryTag parameters.
The value is a JSON object where the key is an API tag from the contract and the value is an object with httpClientTag and telemetryTag fields.
Set configOptions.tags:
Implicit Headers¶
By default, headers from an OpenAPI operation become generated method arguments.
If some headers are supplied by infrastructure rather than application code, they can be made implicit.
implicitHeaders = truemakes all headers fromOpenAPIoperations implicit.implicitHeadersRegexmakes only headers whose names match the regular expression implicit.
An implicit header is removed from the method signature but remains in OpenAPI annotations in generated code.
This keeps the header in contract documentation without requiring application code to pass it manually.
Models¶
The generator creates request and response models from OpenAPI schemas.
Optional fields use @Nullable in Java and nullable type T? in Kotlin.
For schemas with inheritance and a discriminator, Java can generate sealed interface, and Kotlin can generate sealed interface / classes depending on the schema.
Optional Nullable Fields¶
If a field is both nullable: true and absent from the required list, it is generated as a normal optional field by default.
If you need to distinguish three states - the field is absent in JSON, the field is present with null, and the field is present with a value - enable enableJsonNullable.
In that case, the field is generated as JsonNullable.
forceIncludeOptional and forceIncludeNonRequired control serialization of optional fields:
forceIncludeOptionalsets@JsonInclude(Always)for fields withnullable: trueandrequired: falseinstead of usingJsonNullable.forceIncludeNonRequiredsets@JsonInclude(Always)for all fields withrequired: false.
forceIncludeOptional cannot be enabled together with enableJsonNullable because both modes solve the same problem in different ways.
Model Filtering¶
OpenAPI Generator can filter operations through openapiNormalizer.FILTER.
If filterWithModels is additionally enabled, the Kora generator tries to exclude unused models that remain after operation filtering.
This is useful for large contracts where an application generates only part of the API.
Server¶
A minimal plugin configuration for creating HTTP server handlers:
For servers, configOptions.mode supports java-server, java-async-server, and java-reactive-server.
Other server parameters are described below in the validation, delegate classes, interceptors, models, and implicit headers sections.
def openApiGenerateHttpServer = tasks.register("openApiGenerateHttpServer", GenerateTask) {
generatorName = "kora"
group = "openapi tools"
inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml" //(1)!
outputDir = "$buildDir/generated/openapi" //(2)!
def corePackage = "ru.tinkoff.kora.example.openapi"
apiPackage = "${corePackage}.api" //(3)!
modelPackage = "${corePackage}.model" //(4)!
invokerPackage = "${corePackage}.invoker" //(5)!
openapiNormalizer = [
DISABLE_ALL: "true"
]
configOptions = [
mode: "java-server", //(6)!
]
}
sourceSets.main { java.srcDirs += openApiGenerateHttpServer.get().outputDir } //(7)!
compileJava.dependsOn openApiGenerateHttpServer //(8)!
- Path to the
OpenAPIfile used to create classes - Directory where generated files are created
- Package for delegates, controllers, and mappers
- Package for models and DTOs
- Auxiliary generator package
- Plugin mode
- Register generated classes as project source code
- Make code compilation depend on HTTP server class generation: generate first, compile after
For servers, configOptions.mode supports kotlin-server and kotlin-suspend-server.
Other server parameters are described below in the validation, delegate classes, interceptors, models, and implicit headers sections.
val openApiGenerateHttpServer = tasks.register<GenerateTask>("openApiGenerateHttpServer") {
generatorName = "kora"
group = "openapi tools"
inputSpec = "$projectDir/src/main/resources/openapi/openapi.yaml" //(1)!
outputDir = "$buildDir/generated/openapi" //(2)!
val corePackage = "ru.tinkoff.kora.example.openapi"
apiPackage = "${corePackage}.api" //(3)!
modelPackage = "${corePackage}.model" //(4)!
invokerPackage = "${corePackage}.invoker" //(5)!
openapiNormalizer = mapOf(
"DISABLE_ALL" to "true"
)
configOptions = mapOf(
"mode" to "kotlin-server" //(6)!
)
}
kotlin.sourceSets.main { kotlin.srcDir(openApiGenerateHttpServer.get().outputDir) } //(7)!
tasks.withType<KspTask> { dependsOn(openApiGenerateHttpServer) } //(8)!
- Path to the
OpenAPIfile used to create classes - Directory where generated files are created
- Package for delegates, controllers, and mappers
- Package for models and DTOs
- Auxiliary generator package
- Plugin mode
- Register generated classes as project source code
- Make code compilation depend on HTTP server class generation: generate first, compile after
After generation, handlers are registered automatically.
Validation¶
To generate models and controllers with annotations from the validation module, set enableServerValidation:
When enableServerValidation is enabled, the generator adds validation annotations to models and server method parameters,
and also adds @Validate to controller methods with validated parameters.
enableServerValidationInterceptor controls adding ValidationHttpServerInterceptor, which converts validation errors to HTTP responses.
If enableServerValidationInterceptor is not specified explicitly, it is considered enabled when server validation is enabled.
If enableServerValidationInterceptor = false is specified, validation annotations remain, but the standard response interceptor is not added.
Delegate Implementation¶
The server generator creates a controller and a delegate contract where the user implements application logic.
By default, delegateMethodBodyMode = none, so delegate contract methods do not get a standard body and must be implemented by the application.
If delegateMethodBodyMode = throwException is set, methods get a body that throws an exception, and the generator also creates a module
with a default delegate contract implementation. This mode is useful when the application must be built before all operations are implemented, or when custom implementations are connected gradually.
Delegate Response Types¶
Each generated delegate method returns a sealed *ApiResponses envelope whose subtypes encode the HTTP status declared in the contract.
For an operation getPetById with responses 200 and 404, the generator produces PetApiResponses.GetPetByIdApiResponse with subtypes
GetPetById200ApiResponse (carrying the body via content()) and GetPetById404ApiResponse. The implementation returns the subtype matching the outcome:
Return type depends on mode: java-server returns the value directly (shown here), java-async-server returns CompletionStage<...>, java-reactive-server returns Mono<...>:
@Component
public final class PetDelegate implements PetApiDelegate {
private final Map<Long, Pet> petMap = new ConcurrentHashMap<>();
@Override
public PetApiResponses.GetPetByIdApiResponse getPetById(long petId) {
var pet = petMap.get(petId);
if (pet == null) {
return new PetApiResponses.GetPetByIdApiResponse.GetPetById404ApiResponse(); //(1)!
}
return new PetApiResponses.GetPetByIdApiResponse.GetPetById200ApiResponse(pet); //(2)!
}
@Override
public PetApiResponses.AddPetApiResponse addPet(Pet body) {
petMap.put(body.id(), body);
return new PetApiResponses.AddPetApiResponse.AddPet200ApiResponse(body);
}
}
- Status
404subtype, no body - Status
200subtype carrying the response body
Return type depends on mode: kotlin-server returns the value directly (shown here), kotlin-suspend-server uses a suspend method:
@Component
class PetDelegate : PetApiDelegate {
private val petMap = ConcurrentHashMap<Long, Pet>()
override fun getPetById(petId: Long): PetApiResponses.GetPetByIdApiResponse {
val pet = petMap[petId]
return if (pet == null) {
PetApiResponses.GetPetByIdApiResponse.GetPetById404ApiResponse() //(1)!
} else {
PetApiResponses.GetPetByIdApiResponse.GetPetById200ApiResponse(pet) //(2)!
}
}
override fun addPet(pet: Pet): PetApiResponses.AddPetApiResponse {
petMap[pet.id] = pet
return PetApiResponses.AddPetApiResponse.AddPet200ApiResponse(pet)
}
}
- Status
404subtype, no body - Status
200subtype carrying the response body
Raw Request in Delegate¶
By default, a delegate method receives only the parameters declared in the contract. If an implementation needs access to the raw request
(for example to read an infrastructure header or the remote address), enable requestInDelegateParams. The generator then adds an
HttpServerRequest as the first parameter of every delegate method. This is a server-only option.
- Adds
HttpServerRequest _serverRequestas the first argument of each delegate method
Controller Path Prefix¶
prefixPath prepends a base path to every generated HTTP server controller route. It is useful when all operations should be served under a common
segment (for example /api/v1) that is not part of the OpenAPI paths.
Interceptors¶
Generated controllers annotated with @HttpController can also be annotated with interceptors.
The value is a JSON object where the key is an API tag from the contract and the value is an object with type and tag fields.
Both fields can be specified together, or only one of them can be specified:
type- implementation class of a concrete interceptortag- interceptor tags, either a string or an array of strings
Set configOptions.interceptors:
Authorization¶
When the OpenAPI contract describes securitySchemes, the server generator creates an ApiSecurity module with one marker class per scheme:
ApiSecurity.BearerAuth, ApiSecurity.BasicAuth, ApiSecurity.ApiKeyAuth, and ApiSecurity.OAuth
(handling Basic/ApiKey/Bearer/OAuth).
For each scheme, the application must provide an HttpServerPrincipalExtractor component tagged with the matching marker class.
The extractor receives the request and the parsed credential value and returns the authenticated Principal:
@Module
public interface AuthModule {
@Tag(ApiSecurity.BearerAuth.class)
default HttpServerPrincipalExtractor<Principal> bearerHttpServerPrincipalExtractor() {
return (request, value) -> CompletableFuture.completedFuture(new UserPrincipal("name"));
}
@Tag(ApiSecurity.BasicAuth.class)
default HttpServerPrincipalExtractor<Principal> basicHttpServerPrincipalExtractor() {
return (request, value) -> CompletableFuture.completedFuture(new UserPrincipal("name"));
}
@Tag(ApiSecurity.ApiKeyAuth.class)
default HttpServerPrincipalExtractor<Principal> apiKeyHttpServerPrincipalExtractor() {
return (request, value) -> CompletableFuture.completedFuture(new UserPrincipal("name"));
}
@Tag(ApiSecurity.OAuth.class)
default HttpServerPrincipalExtractor<PrincipalWithScopes> oauthHttpServerPrincipalExtractor() { //(1)!
return (request, value) -> CompletableFuture.completedFuture(new UserPrincipal("name"));
}
}
OAuthschemes declare scopes, so the extractor returns aPrincipalWithScopes
@Module
interface AuthModule {
@Tag(ApiSecurity.BearerAuth::class)
fun bearerHttpServerPrincipalExtractor(): HttpServerPrincipalExtractor<Principal> {
return HttpServerPrincipalExtractor { _, _ -> CompletableFuture.completedFuture(UserPrincipal("name")) }
}
@Tag(ApiSecurity.BasicAuth::class)
fun basicHttpServerPrincipalExtractor(): HttpServerPrincipalExtractor<Principal> {
return HttpServerPrincipalExtractor { _, _ -> CompletableFuture.completedFuture(UserPrincipal("name")) }
}
@Tag(ApiSecurity.ApiKeyAuth::class)
fun apiKeyHttpServerPrincipalExtractor(): HttpServerPrincipalExtractor<Principal> {
return HttpServerPrincipalExtractor { _, _ -> CompletableFuture.completedFuture(UserPrincipal("name")) }
}
@Tag(ApiSecurity.OAuth::class)
fun oauthHttpServerPrincipalExtractor(): HttpServerPrincipalExtractor<PrincipalWithScopes> { //(1)!
return HttpServerPrincipalExtractor { _, _ -> CompletableFuture.completedFuture(UserPrincipal("name")) }
}
}
OAuthschemes declare scopes, so the extractor returns aPrincipalWithScopes
For OAuth, the returned principal must implement PrincipalWithScopes so the generated controller can enforce the scopes declared on each operation.
Only the schemes that the contract actually uses need an extractor; a marker class exists for every declared scheme:
Recommendations¶
Advice
If something is not generated by the plugin, or behavior differs from expectations or from other versions, carefully check the plugin configuration and study the settings, because they can affect how classes are generated.
Starting with plugin version 7.0.0, the SIMPLIFY_ONEOF_ANYOF rule enabled by default in openapiNormalizer
can lead to some non-obvious generator results.