There are cases that configuration property binding does not work when integrating 3rd party configuration class and it deploy to the application server as war file(= When JndiPropertySource is enabled).
Versions
- 2.2.0.RELEASE(2.2.0.M5+) ※2.2.0.M4 work fine
Details
For example, following properties class does not work. (As actually, MyBatis's configuration class matches this pattern...)
package com.example;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
@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;
}
}
package com.example;
public class ThirdPartyConfiguration {
private String encoding;
private boolean enabled;
private final List<ResultMap> resultMaps = new ArrayList<>(); // ### Collection
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 Collection<ResultMap> getResultMaps() {
return resultMaps;
}
public void addResultMap(ResultMap rm) {
resultMaps.add(rm);
}
}
package com.example;
public class ResultMap {
private String name;
private String option;
private ResultMap() { // ### Define private (If define public, this issue does not occurred)
}
public String getName() {
return name;
}
public String getOption() {
return option;
}
public static class Builder { // ### Builder class
private final ResultMap resultMap;
public Builder(String name) {
resultMap = new ResultMap();
resultMap.name = name;
}
public Builder option(String option) {
resultMap.option = option;
return this;
}
public ResultMap build() {
return resultMap;
}
}
}
StackTrace
...
[INFO] [talledLocalContainer] Caused by: org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'my.configuration.result-maps[0]' to com.example.ResultMap
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.handleBindError(Binder.java:337)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:297)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.lambda$null$1(Binder.java:385)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder$Context.withSource(Binder.java:519)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder$Context.access$1000(Binder.java:486)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.lambda$bindAggregate$2(Binder.java:386)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.IndexedElementsBinder.bindIndexed(IndexedElementsBinder.java:106)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.IndexedElementsBinder.bindIndexed(IndexedElementsBinder.java:86)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.IndexedElementsBinder.bindIndexed(IndexedElementsBinder.java:71)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.CollectionBinder.bindAggregate(CollectionBinder.java:49)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.AggregateBinder.bind(AggregateBinder.java:56)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.lambda$bindAggregate$3(Binder.java:388)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:543)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder$Context.access$200(Binder.java:486)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.bindAggregate(Binder.java:388)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:349)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:293)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$4(Binder.java:421)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:88)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:77)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:54)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$5(Binder.java:425)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:543)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder$Context.withDataObject(Binder.java:529)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder$Context.access$500(Binder.java:486)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.bindDataObject(Binder.java:423)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:364)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:293)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$4(Binder.java:421)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:88)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:77)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:54)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$5(Binder.java:425)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:543)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder$Context.withDataObject(Binder.java:529)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder$Context.access$500(Binder.java:486)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.bindDataObject(Binder.java:423)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:364)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:293)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:281)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:211)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:198)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.bind(ConfigurationPropertiesBinder.java:89)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:107)
[INFO] [talledLocalContainer] ... 60 more
[INFO] [talledLocalContainer] Caused by: java.lang.IllegalStateException: Failed to extract parameter names for com.example.ResultMap(com.example.ResultMap$1)
[INFO] [talledLocalContainer] at org.springframework.util.Assert.state(Assert.java:94)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.ValueObjectBinder$DefaultValueObject.parseConstructorParameters(ValueObjectBinder.java:178)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.ValueObjectBinder$DefaultValueObject.<init>(ValueObjectBinder.java:173)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.ValueObjectBinder$DefaultValueObject.get(ValueObjectBinder.java:218)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.ValueObjectBinder$DefaultValueObject.get(ValueObjectBinder.java:206)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.ValueObjectBinder$ValueObject.get(ValueObjectBinder.java:112)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.ValueObjectBinder.bind(ValueObjectBinder.java:51)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$5(Binder.java:425)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:543)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder$Context.withDataObject(Binder.java:529)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder$Context.access$500(Binder.java:486)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.bindDataObject(Binder.java:423)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:364)
[INFO] [talledLocalContainer] at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:293)
Related Issue
-
17098
Reproduce project
How to reproduce
$ ./mvnw package corgo:run
Comment From: kazuki43zoo
I've attached a small reproduce project.
Comment From: slavus
I have the same issue. Does someone have a workaround ?
Comment From: kazuki43zoo
@slavus
If you does not use the JDNI feature, probably you can prevent this issue by disabled the Spring's JNDI feature. How to disabled, See https://github.com/mybatis/spring-boot-starter/issues/350#issuecomment-500480499.
Comment From: slavus
Unfortunately our app is deployed to an application server and it utilize jndi for external configuration, so this workaround does not help us.
Comment From: mbhave
@kazuki43zoo Thank you for the sample. To get the sample to fail, I had to add my.configuration.result-maps[0].name to application.properties. However, I wasn't able to get it to work even with 2.2.0.M4. The error I get if I change the version to 2.2.0M4 is
Description:
Failed to bind properties under 'my.configuration.result-maps[0]' to com.example.ResultMap:
Property: my.configuration.result-maps[0].name
Value: hello
Origin: class path resource [application.properties]:2:38
Reason: No setter found for property: name
I'm not sure there's anything we can do here if no setter is found for the property. Adding for: team-attention to see what the rest of the team thinks.
Comment From: kazuki43zoo
@mbhave
Is deploying on the application server(e.g. Tomcat)? The demo can run on Tomcat using Cargo maven plugin as follow:
$ ./mvnw package cargo:run
It is failing without adding 'my.configuration.result-maps[0]' on application.properties.
Comment From: mbhave
Thanks @kazuki43zoo we were able to reproduce the behavior you mentioned when deploying it as a war. There are a couple of issues here that got introduced when constructor binding was added.
-
Since
ResultMaphas a private constructor and a nested class, there is a synthetic constructor that ends up being created with an anonymous class as the first parameter. (Thanks to @wilkinsona for digging into why that happens). This causes constructor binding to kick in forResultMapwhich fails because it can't find the parameters for that synthetic constructor. We should we able to get around this by ignoring synthetic constructors. -
Constructor binding should not have been attempted with
ResultMapin the first place since there is no@ConstructorBindingannotation specified on the top-level properties or the nested properties.
Comment From: kazuki43zoo
@mbhave @philwebb I've confirmed to fix this issue using my demo app. Thanks for your work.
Comment From: Zhoutzzz
Actually I also meet this issue at spring -boot version 2.7.18 when it comes to I use mybatis as my ORM framework at my private project, that's wired! I recreate a new project just dependency the required dependencies with spring-boot 2.7.18 and mybatis-spring-boot-starter that the version match spring-boot. finally I can not reproduce this issue. And how should I do if I can not rewrite mybatis code to add @ConstructorBinding annotation for it and I want to fix this issue at the same time I also can not decline spring boot and mybatis version? By the way, Does anyone know why there is a synthetic constructor that ends up being created with an anonymous class as the first parameter?
Comment From: bclozel
@Zhoutzzz Spring Boot 2.7.x is not supported anymore for OSS users. Please upgrade to a supported version.
Comment From: Zhoutzzz
@bclozel But the version already fixed, we don't have permission to change it, we must be need to find other way to resolve this issue. By the way we meet this issue because we use the code like this
// this method implement the method of EnvironmentAware interface
@Override
public void setEnvironment(Environment environment) {
Binder bind = Binder.get(environment);
this.global = bind.bind("mybatis", MybatisProperties.class);
}