Skip to content

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

Closed
wants to merge 2 commits into from

Conversation

qxo
Copy link
Contributor

@qxo 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

```
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
```
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Mar 4, 2022
@wilkinsona
Copy link
Member

Thanks for the pull request, @qxo.

It looks like you have a null @ConfigurationProperties bean but I cannot see how that can happen. Any null @ConfigurationProperties bean in the context doesn't appear to be included in the map that's returned from ConfigurationPropertiesBean.getAll(ConfigurableApplicationContext). Can you please provide a sample or, even better, add a test to ConfigurationPropertiesReportEndpointTests that reproduces the problem?

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 ConfigurationPropertiesReportEndpoint.configurationPropertiesWithPrefix(String).

@wilkinsona wilkinsona added the status: waiting-for-feedback We need additional information before we can continue label Mar 4, 2022
@qxo
Copy link
Contributor Author

qxo commented Mar 6, 2022

Thanks for the pull request, @qxo.

It looks like you have a null @ConfigurationProperties bean but I cannot see how that can happen. Any null @ConfigurationProperties bean in the context doesn't appear to be included in the map that's returned from ConfigurationPropertiesBean.getAll(ConfigurableApplicationContext). Can you please provide a sample or, even better, add a test to ConfigurationPropertiesReportEndpointTests that reproduces the problem?

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 ConfigurationPropertiesReportEndpoint.configurationPropertiesWithPrefix(String).

I'm not sure how to reproduce the problem.
It's fix our private project.

I'll try reproduce the problem in testcase:)

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Mar 6, 2022
@wilkinsona wilkinsona added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Mar 6, 2022
@snicoll
Copy link
Member

snicoll commented Mar 21, 2022

@qxo how is it going?

@qxo
Copy link
Contributor Author

qxo commented Mar 22, 2022

@qxo how is it going?

see the poc: https://github.com/qxo/springboot-actuator-configprops-npe-poc

  • ConfigurationPropertiesBean.getAll should ignored the null value (ConfigurationPropertiesBean) in the return Map.

  • In @ConfigurationProperties bean class some @bean do not have ConfigurationProperties annotation will trigger the issue.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Mar 22, 2022
@qxo qxo force-pushed the fix-4-configprops-NPE branch from 07e7eae to d87b625 Compare March 22, 2022 13:38
@wilkinsona
Copy link
Member

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 spring-boot-starter-actuator, accessing http://127.0.0.1:8081/actuator/configprops results in a 200 response with no sign of the NPE.

The code in the sample is also quite unusual as @Configuration and @ConfigurationProperties should not be used on the same class.

Can you please update the sample so that it reproduces the NPE?

@wilkinsona wilkinsona added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Mar 22, 2022
@wilkinsona
Copy link
Member

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.

@wilkinsona wilkinsona closed this Apr 6, 2022
@wilkinsona wilkinsona added status: declined A suggestion or change that we don't feel we should currently apply and removed status: waiting-for-feedback We need additional information before we can continue status: waiting-for-triage An issue we've not yet triaged labels Apr 6, 2022
@qxo
Copy link
Contributor Author

qxo commented Apr 6, 2022

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
I was forget the commit the feedback to the pull:(

@wilkinsona
Copy link
Member

wilkinsona commented Apr 6, 2022

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 null beans. Filtering them out may not be the right place to address the problem.

@wilkinsona wilkinsona reopened this Apr 6, 2022
@wilkinsona wilkinsona added status: waiting-for-triage An issue we've not yet triaged and removed status: declined A suggestion or change that we don't feel we should currently apply labels Apr 6, 2022
@wilkinsona
Copy link
Member

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 {

    }

}

beanDefinedInInstanceMethodDoesNotHaveAnnotationsFromItsConfigurationClass passes but beanDefinedInStaticMethodDoesNotHaveAnnotationsFromItsConfigurationClass fails.

I'll ask the Framework team to take a look.

@wilkinsona
Copy link
Member

wilkinsona commented Apr 7, 2022

The Framework team agree that the behavior of findAnnotationOnBean is a bug. However, it has behaved as it currently does for over 10 years so they understandably don't want to change it in 5.x. We'll have to work around the current behaviour.

@wilkinsona wilkinsona changed the title fix: NPE for /actuator/configprops NullPointException thrown when accessing /actuator/configprops if a class annotated with both @Configuration and @ConfigurationProperties has a static @Bean method Apr 7, 2022
@wilkinsona wilkinsona changed the title NullPointException thrown when accessing /actuator/configprops if a class annotated with both @Configuration and @ConfigurationProperties has a static @Bean method NullPointerException is thrown when accessing /actuator/configprops if a class annotated with both @Configuration and @ConfigurationProperties has a static @Bean method Apr 7, 2022
@wilkinsona wilkinsona added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels Apr 7, 2022
@wilkinsona wilkinsona self-assigned this Apr 7, 2022
@wilkinsona wilkinsona added this to the 2.5.x milestone Apr 7, 2022
@wilkinsona wilkinsona modified the milestones: 2.5.x, 2.5.13 Apr 7, 2022
wilkinsona pushed a commit that referenced this pull request Apr 7, 2022
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
wilkinsona added a commit that referenced this pull request Apr 7, 2022
@wilkinsona wilkinsona closed this in d9b0efb Apr 7, 2022
@wilkinsona
Copy link
Member

Thanks very much, @qxo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants