From 9485162e807c2ca552a283a3c758c7dac4311a02 Mon Sep 17 00:00:00 2001 From: Vasily Pelikh <2010720+vpelikh@users.noreply.github.com> Date: Tue, 14 Jul 2026 12:10:37 +0300 Subject: [PATCH] Fix NoClassDefFoundError for kotlin.reflect on pure-Java projects --- .../SpringDocKotlinConfiguration.kt | 12 +++--- .../SpringDocKotlinConfigurationTest.java | 43 +++++++++++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 springdoc-openapi-starter-common/src/test/java/org/springdoc/core/configuration/SpringDocKotlinConfigurationTest.java diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocKotlinConfiguration.kt b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocKotlinConfiguration.kt index eddd7927b..6a6d40218 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocKotlinConfiguration.kt +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/configuration/SpringDocKotlinConfiguration.kt @@ -69,12 +69,6 @@ class SpringDocKotlinConfiguration() { .addDeprecatedType(Deprecated::class.java) } - @Bean - @Lazy(false) - fun kotlinRequiredPropertyCustomizer(objectMapperProvider: ObjectMapperProvider): KotlinRequiredPropertyCustomizer { - return KotlinRequiredPropertyCustomizer(objectMapperProvider) - } - @ConditionalOnClass(name = ["kotlin.reflect.full.KClasses"]) class KotlinReflectDependingConfiguration { @@ -98,6 +92,12 @@ class SpringDocKotlinConfiguration() { fun kotlinModelConverter(objectMapperProvider: ObjectMapperProvider): KotlinInlineClassUnwrappingConverter { return KotlinInlineClassUnwrappingConverter(objectMapperProvider) } + @Bean + @Lazy(false) + @ConditionalOnMissingBean + fun kotlinRequiredPropertyCustomizer(objectMapperProvider: ObjectMapperProvider): KotlinRequiredPropertyCustomizer { + return KotlinRequiredPropertyCustomizer(objectMapperProvider) + } @Bean @ConditionalOnProperty( diff --git a/springdoc-openapi-starter-common/src/test/java/org/springdoc/core/configuration/SpringDocKotlinConfigurationTest.java b/springdoc-openapi-starter-common/src/test/java/org/springdoc/core/configuration/SpringDocKotlinConfigurationTest.java new file mode 100644 index 000000000..c952d47ac --- /dev/null +++ b/springdoc-openapi-starter-common/src/test/java/org/springdoc/core/configuration/SpringDocKotlinConfigurationTest.java @@ -0,0 +1,43 @@ +package org.springdoc.core.configuration; + +import org.junit.jupiter.api.Test; +import org.springdoc.core.customizers.KotlinRequiredPropertyCustomizer; +import org.springdoc.core.properties.SpringDocConfigProperties; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Regression test for GitHub issue #54. + * + *

The {@code kotlinRequiredPropertyCustomizer} bean must only be created when + * {@code kotlin-reflect} is on the classpath. Previously it was declared directly in + * {@link SpringDocKotlinConfiguration} (gated only by kotlin-stdlib presence), which forced a + * {@code NoClassDefFoundError: kotlin/reflect/full/KClasses} for pure-Java projects that have + * kotlin-stdlib but not kotlin-reflect on the classpath.

+ */ +class SpringDocKotlinConfigurationTest { + + private final WebApplicationContextRunner runner = new WebApplicationContextRunner() + .withPropertyValues("springdoc.api-docs.enabled=true") + .withConfiguration(AutoConfigurations.of( + WebMvcAutoConfiguration.class, + SpringDocConfiguration.class, + SpringDocConfigProperties.class, + SpringDocKotlinConfiguration.class)); + + @Test + void kotlinRequiredPropertyCustomizerRegisteredWhenKotlinReflectPresent() { + runner.run(context -> assertThat(context).hasBean("kotlinRequiredPropertyCustomizer")); + } + + @Test + void kotlinRequiredPropertyCustomizerNotRegisteredWhenKotlinReflectAbsent() { + runner.withClassLoader(new FilteredClassLoader("kotlin.reflect.full.KClasses")) + .run(context -> assertThat(context).doesNotHaveBean(KotlinRequiredPropertyCustomizer.class)); + } +}