Describe the bug
SwaggerSchemaMapper.assignType calls Set.of(schemaType) when the swagger Schema has a null type. Set.of rejects null, so any property whose schema is a composed schema (allOf / oneOf / anyOf, which legitimately carry no type) aborts channel and operation scanning with an NPE.
The failure is caught and logged as a WARN, so the published AsyncAPI document silently loses every channel the scanner had not reached yet. In our service 9 of 15 channels disappeared, and the only signal was four WARN lines at startup — the message body is literally null, so there was nothing to identify the offending payload with.
springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/schemas/SwaggerSchemaMapper.java (unchanged on main at the time of writing):
203 private static void assignType(Schema swaggerSchema, SchemaObject.SchemaObjectBuilder builder, boolean isNullable) {
204 Set<String> schemaTypes = swaggerSchema.getTypes();
205 Set<String> types = schemaTypes == null ? new HashSet<>() : new HashSet<>(schemaTypes);
206
207 String schemaType = swaggerSchema.getType();
208 if (!types.contains(schemaType)) {
209 // required while swagger v2 does not populate types
210 builder.type(Set.of(schemaType)); // NPE when schemaType == null
211 return;
212 }
Lines 205 and 208 already tolerate nulls (HashSet.contains(null) returns false), so a type-less schema is routed straight into the one call that cannot accept null.
How we ran into it
springdoc-openapi 3.1.0 added KotlinNullablePropertyCustomizer, which implements io.swagger.v3.core.converter.ModelConverter and is registered as a bean. ModelConvertersProvider:40 adds every ModelConverter bean in the Spring context to springwolf's converters, so it gets applied to Kafka payload schemas too.
For a nullable Kotlin property of object type it replaces the property schema with a composed wrapper (allOf: [$ref] + nullable under OAS 3.0, oneOf: [$ref, {type: "null"}] under 3.1). That wrapper has neither type nor $ref, which is valid OpenAPI, and assignType then throws.
springdoc is just how we hit it — any converter emitting a type-less composed schema would do the same.
Suggested fix
builder.type(schemaType == null ? Set.of() : Set.of(schemaType));
Separately, it would help a lot if DefaultChannelsService / DefaultOperationsService logged the schema or class being scanned when they catch an exception. The current WARN carries only null, which meant tracking this down required decompiling to find out which payload failed.
Dependencies and versions used
Code example
Payload (any nullable, object-typed property will do):
data class AuthorisationPayload(
val serviceReference: String,
val merchant: Merchant?, // nullable + object type -> composed schema with no type
) {
data class Merchant(val settingsId: String)
}
Reproducer without booting Spring — this throws:
val properties = SpringwolfConfigProperties()
val provider = ModelConvertersProvider(
properties,
listOf(KotlinNullablePropertyCustomizer(ObjectMapperProvider(SpringDocConfigProperties()))),
)
val service = SwaggerSchemaService(properties, emptyList(), SwaggerSchemaMapper(properties), provider)
service.resolveSchema(AuthorisationPayload::class.java, "application/json") // NullPointerException
In a running application the same thing happens during startup scanning, with the customizer picked up automatically as a bean. Removing that single bean (springdoc.model-converters.kotlin-nullable-property-customizer.enabled: false) restores all 15 channels, which is our current workaround.
Stack trace and error logs
WARN i.g.s.c.a.c.DefaultChannelsService : An error was encountered during channel scanning with
ChannelsInClassScannerAdapter@2ada96fd: null
WARN i.g.s.c.a.o.DefaultOperationsService : An error was encountered during operation scanning with
OperationsInClassScannerAdapter@41820105: null
java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:220)
at java.base/java.util.ImmutableCollections$Set12.<init>(ImmutableCollections.java:1009)
at java.base/java.util.Set.of(Set.java:470)
at io.github.springwolf.core.asyncapi.schemas.SwaggerSchemaMapper.assignType(SwaggerSchemaMapper.java:210)
at io.github.springwolf.core.asyncapi.schemas.SwaggerSchemaMapper.mapSwaggerSchemaToAsyncApiSchema(SwaggerSchemaMapper.java:100)
at io.github.springwolf.core.asyncapi.schemas.SwaggerSchemaMapper.mapSchema(SwaggerSchemaMapper.java:72)
at io.github.springwolf.core.asyncapi.schemas.SwaggerSchemaMapper.mapSchemaOrRef(SwaggerSchemaMapper.java:51)
at io.github.springwolf.core.asyncapi.schemas.SwaggerSchemaMapper.lambda$mapSwaggerSchemaToAsyncApiSchema$1(SwaggerSchemaMapper.java:105)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
...
Describe the bug
SwaggerSchemaMapper.assignTypecallsSet.of(schemaType)when the swaggerSchemahas a nulltype.Set.ofrejects null, so any property whose schema is a composed schema (allOf/oneOf/anyOf, which legitimately carry notype) aborts channel and operation scanning with an NPE.The failure is caught and logged as a WARN, so the published AsyncAPI document silently loses every channel the scanner had not reached yet. In our service 9 of 15 channels disappeared, and the only signal was four WARN lines at startup — the message body is literally
null, so there was nothing to identify the offending payload with.springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/schemas/SwaggerSchemaMapper.java(unchanged onmainat the time of writing):Lines 205 and 208 already tolerate nulls (
HashSet.contains(null)returnsfalse), so a type-less schema is routed straight into the one call that cannot accept null.How we ran into it
springdoc-openapi 3.1.0 added
KotlinNullablePropertyCustomizer, which implementsio.swagger.v3.core.converter.ModelConverterand is registered as a bean.ModelConvertersProvider:40adds everyModelConverterbean in the Spring context to springwolf's converters, so it gets applied to Kafka payload schemas too.For a nullable Kotlin property of object type it replaces the property schema with a composed wrapper (
allOf: [$ref]+nullableunder OAS 3.0,oneOf: [$ref, {type: "null"}]under 3.1). That wrapper has neithertypenor$ref, which is valid OpenAPI, andassignTypethen throws.springdoc is just how we hit it — any converter emitting a type-less composed schema would do the same.
Suggested fix
Separately, it would help a lot if
DefaultChannelsService/DefaultOperationsServicelogged the schema or class being scanned when they catch an exception. The current WARN carries onlynull, which meant tracking this down required decompiling to find out which payload failed.Dependencies and versions used
springwolf-coreversion2.6.0(code path unchanged onmain)springwolf-kafkaversion2.6.0springdoc-openapi-starter-webflux-apiversion3.1.04.1.0, Kotlin2.3.10, JVM 25Code example
Payload (any nullable, object-typed property will do):
Reproducer without booting Spring — this throws:
In a running application the same thing happens during startup scanning, with the customizer picked up automatically as a bean. Removing that single bean (
springdoc.model-converters.kotlin-nullable-property-customizer.enabled: false) restores all 15 channels, which is our current workaround.Stack trace and error logs