feat: auto-set nullable: true for Kotlin nullable types in schema properties#3256
feat: auto-set nullable: true for Kotlin nullable types in schema properties#3256thejeff77 wants to merge 2 commits intospringdoc:mainfrom
Conversation
…perties Springdoc correctly uses Kotlin reflection to detect nullable types (isMarkedNullable) for the required list — nullable fields are excluded from `required`. However, it does not set `nullable: true` on the property schema itself. This causes OpenAPI client generators (e.g., fabrikt, openapi-generator) to produce non-null types with null defaults, which fails Kotlin compilation. Adds KotlinNullablePropertyCustomizer that inspects Kotlin data class properties via kotlin-reflect and sets nullable: true on schema properties whose return type is marked nullable. Auto-registered in SpringDocKotlinConfiguration when kotlin-reflect is on the classpath. Fixes: springdoc#906
|
Hi, are you sure that the client generator supports the OAS 3.0 structure "nullableNested": {
"nullable": true,
"$ref": "#/components/schemas/NestedObject"
}? Siblings are not supported in OAS 3.0, like how in 3.1, and more often than not any definitions next to "nullableNested": {
"nullable": true,
"allOf": [
{ "$ref": "#/components/schemas/NestedObject" }
]
}On the subject of OAS versions. Could you also add a test that shows the behavior for 3.1? Since nullability is expressed completely different in that specification. |
|
@bnasslahsen Hi! This PR adds automatic The implementation follows the exact same pattern as the existing Could you approve the CI workflow run and take a look when you get a chance? The test snapshot may need adjustment once CI runs — happy to iterate. |
Addresses reviewer feedback from @Mattias-Sehlstedt: 1. $ref properties now use allOf wrapper in OAS 3.0 instead of sibling nullable + $ref (which is not supported per spec): `{ nullable: true, allOf: [{ $ref: "..." }] }` 2. OAS 3.1 nullable support added using type arrays for simple types (`type: ["string", "null"]`) and oneOf for $ref types (`oneOf: [{ $ref: "..." }, { type: "null" }]`). 3. Added v31 test (app23) with expected snapshot showing OAS 3.1 nullable semantics alongside the existing v30 test (app18). The ModelConverter detects the spec version from the resolved schema and applies the appropriate nullable strategy.
|
@Mattias-Sehlstedt Great catches — you were right on both counts. I pushed a fix addressing both: 1. "nullableNested": {
"nullable": true,
"allOf": [{ "$ref": "#/components/schemas/NestedObject" }]
}Updated the v30 test snapshot (app18) accordingly. 2. OAS 3.1 support added For simple types: The We verified all of this by running our service locally and inspecting the actual generated spec — discovered the OAS 3.1 issue the hard way when |
Problem
Springdoc correctly uses Kotlin reflection (
isMarkedNullable()viaSpringDocKotlinUtils.kotlinNullability()) to detect nullable types for the required list — nullable fields are excluded fromrequiredinSchemaUtils.fieldRequired(). However, it does not mark the property schema itself as nullable.This causes OpenAPI client generators (e.g., fabrikt, openapi-generator) to produce non-null types with null defaults when generating Kotlin clients, which fails compilation:
Solution
Adds
KotlinNullablePropertyCustomizer— aModelConverterthat inspects Kotlin data class properties viakotlin-reflectand marks nullable properties in the schema.Handles both OAS versions:
OAS 3.0 (
nullable: true)Simple types:
{ "type": "string", "nullable": true }$reftypes useallOfwrapper (since$refandnullableare mutually exclusive siblings in OAS 3.0):{ "nullable": true, "allOf": [{ "$ref": "#/components/schemas/NestedObject" }] }OAS 3.1 (
typearrays)Simple types:
{ "type": ["string", "null"] }$reftypes useoneOf:{ "oneOf": [{ "$ref": "#/components/schemas/NestedObject" }, { "type": "null" }] }The
ModelConverterdetects the spec version fromschema.specVersionand applies the correct strategy.Changes
KotlinNullablePropertyCustomizer.ktSpringDocKotlinConfiguration.ktKotlinNullablePropertyCustomizerbeanv30/app18/nullable: trueandallOfwrappingv31/app23/typearrays andoneOfwrappingAuto-registered in
SpringDocKotlinConfiguration.KotlinReflectDependingConfigurationwhenkotlin-reflectis on the classpath, following the same pattern as the existingKotlinDeprecatedPropertyCustomizer.Test
Tests verify that a controller returning a data class with nullable fields produces a spec where:
requiredField: String) are in therequiredlist and NOT marked nullablenullableString: String?,nullableInt: Int?) are marked nullable$reffields (nullableNested: NestedObject?) are wrapped appropriately for each OAS versionFixes #906