When I have the following auto configuration

@Configuration
public class DemoAutoConfiguration {

    @Bean
    @ConditionalOnClass(DemoCommandLineRunner.class)
    public CommandLineRunner demoCommandLineRunner() {
        return new DemoCommandLineRunner();
    }
}

My application cannot start on Java 8 due to

java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy

On Java 11 this problem is not there. The return type is not the same as the one in ConditionalOnClass.

This is clearly some kind of difference in the way annotations are loaded in Java 8 vs Java 11. I am creating this here in case there is something that could be done in Spring Boot or Spring Core that would fix this behaviour between Java 8 and Java 11.

I have also created a minimal project that can be used to see this problem. The project is located here

Comment From: mbhave

The javadoc for @ConditionalOnClass calls out being extra careful when placing it on a @Bean method. See also Andy's comment in a previous issue. I think based on that there is nothing that we can do in Spring Boot. Leaving the issue open to get the team's thoughts.

Comment From: filiphr

Thanks for the link to the other issue @mbhave. I didn't find that one.

I've read the Javadoc and I thought that it is more linked towards the return type being available when the class is loaded. However, in this case the return type is available. The class can be safely loaded. The problem is then the Spring Core AnnotationsScanner.

The full cause is

Caused by: java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy
    at sun.reflect.annotation.AnnotationParser.parseClassArray(AnnotationParser.java:724) ~[na:1.8.0_292]
    at sun.reflect.annotation.AnnotationParser.parseArray(AnnotationParser.java:531) ~[na:1.8.0_292]
    at sun.reflect.annotation.AnnotationParser.parseMemberValue(AnnotationParser.java:355) ~[na:1.8.0_292]
    at sun.reflect.annotation.AnnotationParser.parseAnnotation2(AnnotationParser.java:286) ~[na:1.8.0_292]
    at sun.reflect.annotation.AnnotationParser.parseAnnotations2(AnnotationParser.java:120) ~[na:1.8.0_292]
    at sun.reflect.annotation.AnnotationParser.parseAnnotations(AnnotationParser.java:72) ~[na:1.8.0_292]
    at java.lang.reflect.Executable.declaredAnnotations(Executable.java:602) ~[na:1.8.0_292]
    at java.lang.reflect.Executable.declaredAnnotations(Executable.java:600) ~[na:1.8.0_292]
    at java.lang.reflect.Executable.getDeclaredAnnotations(Executable.java:588) ~[na:1.8.0_292]
    at java.lang.reflect.Method.getDeclaredAnnotations(Method.java:630) ~[na:1.8.0_292]
    at org.springframework.core.annotation.AnnotationsScanner.getDeclaredAnnotations(AnnotationsScanner.java:454) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.core.annotation.AnnotationsScanner.isKnownEmpty(AnnotationsScanner.java:492) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.core.annotation.TypeMappedAnnotations.from(TypeMappedAnnotations.java:251) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.core.annotation.MergedAnnotations.from(MergedAnnotations.java:351) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.core.annotation.MergedAnnotations.from(MergedAnnotations.java:330) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.core.annotation.AnnotatedElementUtils.findAnnotations(AnnotatedElementUtils.java:764) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.core.annotation.AnnotatedElementUtils.hasAnnotation(AnnotatedElementUtils.java:531) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.context.annotation.BeanAnnotationHelper.isBeanAnnotated(BeanAnnotationHelper.java:41) ~[spring-context-5.3.9.jar:5.3.9]
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.isMatch(ConfigurationClassEnhancer.java:407) ~[spring-context-5.3.9.jar:5.3.9]
    at org.springframework.context.annotation.ConfigurationClassEnhancer$ConditionalCallbackFilter.accept(ConfigurationClassEnhancer.java:192) ~[spring-context-5.3.9.jar:5.3.9]
    at org.springframework.cglib.proxy.Enhancer.emitMethods(Enhancer.java:1217) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:726) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.transform.TransformingClassGenerator.generateClass(TransformingClassGenerator.java:33) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.ClassLoaderAwareGeneratorStrategy.generate(ClassLoaderAwareGeneratorStrategy.java:57) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:358) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54) ~[spring-core-5.3.9.jar:5.3.9]
    at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_292]
    at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:419) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.context.annotation.ConfigurationClassEnhancer.createClass(ConfigurationClassEnhancer.java:137) ~[spring-context-5.3.9.jar:5.3.9]
    at org.springframework.context.annotation.ConfigurationClassEnhancer.enhance(ConfigurationClassEnhancer.java:109) ~[spring-context-5.3.9.jar:5.3.9]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.enhanceConfigurationClasses(ConfigurationClassPostProcessor.java:447) ~[spring-context-5.3.9.jar:5.3.9]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanFactory(ConfigurationClassPostProcessor.java:268) ~[spring-context-5.3.9.jar:5.3.9]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:325) ~[spring-context-5.3.9.jar:5.3.9]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:147) ~[spring-context-5.3.9.jar:5.3.9]
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:746) ~[spring-context-5.3.9.jar:5.3.9]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:564) ~[spring-context-5.3.9.jar:5.3.9]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-2.5.4.jar:2.5.4]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[spring-boot-2.5.4.jar:2.5.4]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:338) ~[spring-boot-2.5.4.jar:2.5.4]
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:123) ~[spring-boot-test-2.5.4.jar:2.5.4]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) ~[spring-test-5.3.9.jar:5.3.9]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) ~[spring-test-5.3.9.jar:5.3.9]
    ... 68 common frames omitted

Most likely there is not much you can do, perhaps the only thing would be to update the Javadoc to mention that @ConditionalClass shouldn't really be used on @Bean methods when you want your application to run on Java 8.

Comment From: mbhave

Thanks for the suggestion. I think we should update the javadoc.

Comment From: mhalbritter

The JavaDoc states

Extra care is required when placed on {@code @Bean} methods, consider isolating the condition in a separate {@code Configuration} class, in particular if the return type of the method matches the {@link #value target of the condition}.

Should we reword it to

Extra care is required when placed on {@code @Bean} methods, consider isolating the condition in a separate {@code Configuration} class.

?

Comment From: wilkinsona

Thanks for looking at this, @mhalbritter. I think we should probably go a bit further than that. The previous paragraph reads:

A Class value can be safely specified on @Configuration classes as the annotation metadata is parsed by using ASM before the class is loaded. If a class reference cannot be used then a name String attribute can be used.

I think we should replace the "Note" with something that builds on that and states that value should not be used when annotating a method and that only name should be used in that case. It's slightly overly restrictive, but I think that's better than getting into the subtleties of the different behaviors of Java 8 and 11. The javadoc for the value and name attributes could also be updated along those lines.

Comment From: mhalbritter

Great, I will do this. We only need to do this for 2.6.x and 2.7.x, as 3.x has Java 17 as a baseline where this limitation no longer applies.