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

Comment From: wilkinsona

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).

Comment From: qxo

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:)

Comment From: snicoll

@qxo how is it going?

Comment From: qxo

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

Comment From: wilkinsona

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?

Comment From: wilkinsona

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.

Comment From: qxo

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:(

Comment From: wilkinsona

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.

Comment From: wilkinsona

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.

Comment From: wilkinsona

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.

Comment From: wilkinsona

Thanks very much, @qxo.