-
Notifications
You must be signed in to change notification settings - Fork 41.2k
NullPointerException is thrown when accessing /actuator/configprops if a class annotated with both @Configuration and @ConfigurationProperties has a static @Bean method #30068
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
qxo
commented
Mar 4, 2022
``` java.lang.NullPointerException at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:177) at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169) at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177) at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133) at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801) at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) at org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.describeBeans(ConfigurationPropertiesReportEndpoint.java:190) at org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.extract(ConfigurationPropertiesReportEndpoint.java:138) at org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint.configurationProperties(ConfigurationPropertiesReportEndpoint.java:124 ```
Thanks for the pull request, @qxo. It looks like you have a I'd like to fully understand the problem as, if the change that you have proposed is needed, I suspect a similar change is also needed in |
I'm not sure how to reproduce the problem. I'll try reproduce the problem in testcase:) |
@qxo how is it going? |
see the poc: https://github.com/qxo/springboot-actuator-configprops-npe-poc
|
…ue (ConfigurationPropertiesBean) in the return Map. ref: https://github.com/qxo/springboot-actuator-configprops-npe-poc
07e7eae
to
d87b625
Compare
Thanks for the sample. Unfortunately, it doesn't reproduce the problem. Accessing http://127.0.0.1:8081/actuator/configprops results in a 404 response as there's no actuator dependency. After adding a dependency on The code in the sample is also quite unusual as Can you please update the sample so that it reproduces the NPE? |
It's been a couple of weeks now. Closing due to the lack of requested feedback. If you find the time to update the sample so that it reproduces the problem, we can take another look. |
please check the repo again |
Thanks for the updated sample. I've reproduced the problem now. As suspected, it occurs with both http://127.0.0.1:8081/actuator/configprops and http://127.0.0.1:8081/actuator/configprops/example.property. Before we decide what to do here, I think we need to understand why we're getting |
This looks like a Framework bug to me. When a bean is defined in a static method, Framework considers the bean to be annotated with annotations on the configuration class. The same is not true when the bean is defined in an instance method. The following tests illustrate this behavior: package com.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
class FindAnnotationOnBeanTests {
@Test
void beanDefinedInInstanceMethodDoesNotHaveAnnotationsFromItsConfigurationClass() {
beanDoesNotHaveAnnotationsFromItsConfigurationClass(InstanceBeanMethodConfiguration.class);
}
@Test
void beanDefinedInStaticMethodDoesNotHaveAnnotationsFromItsConfigurationClass() {
beanDoesNotHaveAnnotationsFromItsConfigurationClass(StaticBeanMethodConfiguration.class);
}
void beanDoesNotHaveAnnotationsFromItsConfigurationClass(Class<?> config) {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(config)) {
ExampleAnnotation annotation = context.getBeanFactory().findAnnotationOnBean("exampleBean",
ExampleAnnotation.class);
assertThat(annotation).isNull();
}
}
@Configuration
@ExampleAnnotation
static class StaticBeanMethodConfiguration {
@Bean
static String exampleBean() {
return "example";
}
}
@Configuration
@ExampleAnnotation
static class InstanceBeanMethodConfiguration {
@Bean
String exampleBean() {
return "example";
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
static @interface ExampleAnnotation {
}
}
I'll ask the Framework team to take a look. |
The Framework team agree that the behavior of |
This works around spring-projects/spring-framework#28298. The bug means that when a @configuration class is annotated with @ConfigurationProperties any bean defined by a static @bean method is considered to be annotated with @ConfigurationProperties. See gh-30068
Thanks very much, @qxo. |