Affects: 6.0.12
I am trying to update from Spring boot 2.7.13 to 3.1.4. We are using this custom annotation to document configuration properties.
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
annotation class DocProperty(
val name: String = "",
val displayName: String = "",
val description: String = "",
val defaultValue: String = "",
val defaultExplanation: String = "",
val children: Array<DocProperty> = [],
val prefix: String = "",
val removedIn: String = "",
val removalReason: String = "",
val hidden: Boolean = false
)
When I run the app, it fails:
2023-10-10T17:17:04.743+02:00 ERROR 22027 --- [ restartedMain] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanDefinitionStoreException: Failed to read candidate component class: URL [jar:file:/Users/jenik/IdeaProjects/tolgee-server/public/backend/data/build/libs/data-local-plain.jar!/io/tolgee/configuration/tolgee/S3Settings.class]
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(ClassPathScanningCandidateComponentProvider.java:463) ~[spring-context-6.0.12.jar:6.0.12]
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:317) ~[spring-context-6.0.12.jar:6.0.12]
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:276) ~[spring-context-6.0.12.jar:6.0.12]
at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:128) ~[spring-context-6.0.12.jar:6.0.12]
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:289) ~[spring-context-6.0.12.jar:6.0.12]
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:243) ~[spring-context-6.0.12.jar:6.0.12]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:196) ~[spring-context-6.0.12.jar:6.0.12]
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:164) ~[spring-context-6.0.12.jar:6.0.12]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:415) ~[spring-context-6.0.12.jar:6.0.12]
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:287) ~[spring-context-6.0.12.jar:6.0.12]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:344) ~[spring-context-6.0.12.jar:6.0.12]
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:115) ~[spring-context-6.0.12.jar:6.0.12]
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:771) ~[spring-context-6.0.12.jar:6.0.12]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:589) ~[spring-context-6.0.12.jar:6.0.12]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.1.4.jar:3.1.4]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737) ~[spring-boot-3.1.4.jar:3.1.4]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.1.4.jar:3.1.4]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-3.1.4.jar:3.1.4]
at io.tolgee.Application$Companion.main(Application.kt:26) ~[main/:na]
at io.tolgee.Application.main(Application.kt) ~[main/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-3.1.4.jar:3.1.4]
Caused by: java.lang.StackOverflowError: null
at java.base/java.lang.ref.ReferenceQueue.poll(ReferenceQueue.java:117) ~[na:na]
Caused by: java.lang.StackOverflowError: null
at org.springframework.util.ConcurrentReferenceHashMap$ReferenceManager.pollForPurge(ConcurrentReferenceHashMap.java:1006) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.util.ConcurrentReferenceHashMap$Segment.restructureIfNecessary(ConcurrentReferenceHashMap.java:574) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.util.ConcurrentReferenceHashMap$Segment.getReference(ConcurrentReferenceHashMap.java:495) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.util.ConcurrentReferenceHashMap.getReference(ConcurrentReferenceHashMap.java:265) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.util.ConcurrentReferenceHashMap.get(ConcurrentReferenceHashMap.java:235) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationsScanner.getDeclaredAnnotations(AnnotationsScanner.java:446) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationsScanner.getDeclaredAnnotation(AnnotationsScanner.java:435) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMapping.resolveAliasedForTargets(AnnotationTypeMapping.java:144) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMapping.<init>(AnnotationTypeMapping.java:122) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.addIfPossible(AnnotationTypeMappings.java:112) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.addAllMappings(AnnotationTypeMappings.java:75) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.<init>(AnnotationTypeMappings.java:68) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings$Cache.createMappings(AnnotationTypeMappings.java:245) ~[spring-core-6.0.12.jar:6.0.12]
at java.base/java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:330) ~[na:na]
at org.springframework.core.annotation.AnnotationTypeMappings$Cache.get(AnnotationTypeMappings.java:241) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:199) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:182) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:169) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMapping.computeSynthesizableFlag(AnnotationTypeMapping.java:405) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMapping.<init>(AnnotationTypeMapping.java:126) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.addIfPossible(AnnotationTypeMappings.java:112) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.addAllMappings(AnnotationTypeMappings.java:75) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.<init>(AnnotationTypeMappings.java:68) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings$Cache.createMappings(AnnotationTypeMappings.java:245) ~[spring-core-6.0.12.jar:6.0.12]
at java.base/java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:330) ~[na:na]
at org.springframework.core.annotation.AnnotationTypeMappings$Cache.get(AnnotationTypeMappings.java:241) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:199) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:182) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:169) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMapping.computeSynthesizableFlag(AnnotationTypeMapping.java:405) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMapping.<init>(AnnotationTypeMapping.java:126) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.addIfPossible(AnnotationTypeMappings.java:112) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.addAllMappings(AnnotationTypeMappings.java:75) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.<init>(AnnotationTypeMappings.java:68) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings$Cache.createMappings(AnnotationTypeMappings.java:245) ~[spring-core-6.0.12.jar:6.0.12]
at java.base/java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:330) ~[na:na]
at org.springframework.core.annotation.AnnotationTypeMappings$Cache.get(AnnotationTypeMappings.java:241) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:199) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:182) ~[spring-core-6.0.12.jar:6.0.12]
at org.springframework.core.annotation.AnnotationTypeMappings.forAnnotationType(AnnotationTypeMappings.java:169) ~[spring-core-6.0.12.jar:6.0.12]
at
...
I've epxlored what happens, and the issue is that method org.springframework.core.annotation.AnnotationTypeMapping#computeSynthesizableFlag
finds the children
property of our DocProperty
class annotation and calls the forAnnotationType
, with the children
's type which is DocProperty
again. And that's infinite loop.
It's reproducible here: https://github.com/tolgee/tolgee-platform/pull/1938/commits/259cd313e625b9e87af26e4f9c0e7a4b96583ada (the specific commit)
Comment From: jhoeller
It looks like #28618 removed the support for cyclic annotation definitions introduced in #28012 but unfortunately also removed the ability to handle nested references to the same annotation type.
Comment From: JanCizmar
Is there any workaround like ignoring the annotation?
Comment From: jhoeller
I'm afraid there isn't an obvious workaround. We'll have to address this in our AnnotationTypeMapping
implementation.
Note that Java annotations are not allowed to refer to themselves in their attributes, so this remains Kotlin specific. Kotlin prohibits deep cycles (X -> Y -> X) in annotation declarations as of Kotlin 1.9 (aligned with Java) but still allows self references (in contrast to Java)... unfortunately we missed the latter part there.
Comment From: lorenzsimon
@jhoeller The fix does not work for the following legal Kotlin code:
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
annotation class DocProperty(
val name: String = "",
val displayName: String = "",
val description: String = "",
val defaultValue: String = "",
val defaultExplanation: String = "",
val children: Array<DocProperty> = [],
val prefix: String = "",
val removedIn: String = "",
val removalReason: String = "",
val hidden: Boolean = false,
val externalMap: Array<DocPropertyMapEntry> = []
)
annotation class DocPropertyMapEntry(
val docProperty: DocProperty = DocProperty()
)
see the externalMap property. Can Spring add support for this valid use-case please 🙏
This is my use-case: https://github.com/OpenFolder/kotlin-asyncapi/blob/master/kotlin-asyncapi-annotation/src/main/kotlin/org/openfolder/kotlinasyncapi/annotation/Schema.kt