Spring-boot: Constructor binding can fail with non iterable property sources

Created on 10 Jun 2019  路  3Comments  路  Source: spring-projects/spring-boot

The constructor binding feature is very cool. But there are cases that does not work fine when integrating 3rd party configuration class and it deploy to the application server as war file(= When JndiPropertySource is enabled).

Versions

  • 2.2.0.M3 and snapshot version
  • Tomcat 9.0.20

Details

For example, following properties class does not work.

@ConfigurationProperties(prefix = "my")
public class MyProperties {

  private String name;

  @NestedConfigurationProperty
  private ThirdPartyConfiguration configuration; // Third party class

  public void setName(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setConfiguration(ThirdPartyConfiguration configuration) {
    this.configuration = configuration;
  }

  public ThirdPartyConfiguration getConfiguration() {
    return configuration;
  }

}
public class ThirdPartyConfiguration {

  private String encoding;

  private boolean enabled;

  private final List<Attribute> attributes = new ArrayList<>();

  public void setEncoding(String encoding) {
    this.encoding = encoding;
  }

  public String getEncoding() {
    return encoding;
  }

  public void setEnabled(boolean enabled) {
    this.enabled = enabled;
  }

  public boolean isEnabled() {
    return enabled;
  }

  public void addAttribute(Attribute attribute) {
    this.attributes.add(attribute);
  }

  public List<Attribute> getAttributes() {
    return attributes;
  }

  public static class Attribute {

    private final String name;
    private final Object value;

    public Attribute(String name, Object value) {
      Objects.requireNonNull(name, "'name' must be not null.");
      Objects.requireNonNull(value, "'value' must be not null.");
      this.name = name;
      this.value = value;
    }

    public String getName() {
      return name;
    }

    public Object getValue() {
      return value;
    }

  }

}
  • application.properties
# empty

Stacktrace

...
Caused by: org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'my.configuration.attributes[0]' to com.example.springbootstartergh350.ThirdPartyConfiguration$Attribute
    at org.springframework.boot.context.properties.bind.Binder.handleBindError(Binder.java:243)
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:219)
    at org.springframework.boot.context.properties.bind.Binder.lambda$null$0(Binder.java:291)
    at org.springframework.boot.context.properties.bind.Binder$Context.withSource(Binder.java:406)
    at org.springframework.boot.context.properties.bind.Binder$Context.access$1000(Binder.java:373)
    at org.springframework.boot.context.properties.bind.Binder.lambda$bindAggregate$1(Binder.java:292)
    at org.springframework.boot.context.properties.bind.IndexedElementsBinder.bindIndexed(IndexedElementsBinder.java:106)
    at org.springframework.boot.context.properties.bind.IndexedElementsBinder.bindIndexed(IndexedElementsBinder.java:86)
    at org.springframework.boot.context.properties.bind.IndexedElementsBinder.bindIndexed(IndexedElementsBinder.java:71)
    at org.springframework.boot.context.properties.bind.CollectionBinder.bindAggregate(CollectionBinder.java:49)
    at org.springframework.boot.context.properties.bind.AggregateBinder.bind(AggregateBinder.java:56)
    at org.springframework.boot.context.properties.bind.Binder.lambda$bindAggregate$2(Binder.java:294)
    at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:430)
    at org.springframework.boot.context.properties.bind.Binder$Context.access$200(Binder.java:373)
    at org.springframework.boot.context.properties.bind.Binder.bindAggregate(Binder.java:294)
    at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:255)
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:215)
    at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$3(Binder.java:327)
    at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:80)
    at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:70)
    at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:54)
    at org.springframework.boot.context.properties.bind.Binder.lambda$null$4(Binder.java:330)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1359)
    at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
    at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464)
    at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$5(Binder.java:331)
    at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:430)
    at org.springframework.boot.context.properties.bind.Binder$Context.withBean(Binder.java:416)
    at org.springframework.boot.context.properties.bind.Binder$Context.access$500(Binder.java:373)
    at org.springframework.boot.context.properties.bind.Binder.bindBean(Binder.java:329)
    at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:270)
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:215)
    at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$3(Binder.java:327)
    at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:80)
    at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:70)
    at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:54)
    at org.springframework.boot.context.properties.bind.Binder.lambda$null$4(Binder.java:330)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1359)
    at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
    at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464)
    at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$5(Binder.java:331)
    at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:430)
    at org.springframework.boot.context.properties.bind.Binder$Context.withBean(Binder.java:416)
    at org.springframework.boot.context.properties.bind.Binder$Context.access$500(Binder.java:373)
    at org.springframework.boot.context.properties.bind.Binder.bindBean(Binder.java:329)
    at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:270)
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:215)
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:203)
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:186)
    at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.bind(ConfigurationPropertiesBinder.java:93)
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:115)
    ... 66 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.springbootstartergh350.ThirdPartyConfiguration$Attribute]: Constructor threw exception; nested exception is java.lang.NullPointerException: 'name' is not null.
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:213)
    at org.springframework.boot.context.properties.bind.ConstructorParametersBinder.bind(ConstructorParametersBinder.java:60)
    at org.springframework.boot.context.properties.bind.Binder.lambda$null$4(Binder.java:330)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1359)
    at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
    at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464)
    at org.springframework.boot.context.properties.bind.Binder.lambda$bindBean$5(Binder.java:331)
    at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:430)
[2019-06-11 03:48:48,181] Artifact spring-boot-starter-gh-350:war: Error during artifact deployment. See server log for details.
    at org.springframework.boot.context.properties.bind.Binder$Context.withBean(Binder.java:416)
    at org.springframework.boot.context.properties.bind.Binder$Context.access$500(Binder.java:373)
    at org.springframework.boot.context.properties.bind.Binder.bindBean(Binder.java:329)
    at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:270)
    at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:215)
    ... 127 more
Caused by: java.lang.NullPointerException: 'name' must be not null.
    at java.util.Objects.requireNonNull(Objects.java:228)
    at com.example.springbootstartergh350.ThirdPartyConfiguration$Attribute.<init>(ThirdPartyConfiguration.java:47)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:200)
    ... 145 more

Repro project

spring-boot-gh-17098-master.zip

bug

All 3 comments

I've added repro project. This issue can reproduce using cargo plugin as follow:

$ ./mvnw package cargo:run

@philwebb Thanks for fix! I've confirmed to fix this issue on 2.2.0M4.

@kazuki43zoo Actually @mbhave fixed it, I just did the merge. Thanks for testing M4.

Was this page helpful?
0 / 5 - 0 ratings