I am running into an issue with unbound properties in my application.yml file. I am in the middle of migrating from Spring Boot 1.5.4 to Spring Boot 2.
My problem is that I have some properties which can optionally be left blank, for instance:
application.yml
app:
enabled: false
url: #ldap://127.0.0.1:3268
user: #admin
In this case if ldap.enabled is set to true, then the ldap properties can be set to the needed values which are currently commented out. However, if ldap.enabled is set to false then the rest of the properties are not set and are left blank.
In my Spring Boot 1.5.4 application I didn't have any problems with this, but now after upgrading to Spring Boot 2 I get the following exceptions:
org.springframework.boot.context.properties.bind.UnboundConfigurationPropertiesException: The elements [ldap.ignorecertificate] were left unbound.
org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'ldap' to com.myapp.server.config.properties.LdapProperties
LdapProperties.java
#Component
#ConfigurationProperties(prefix = "app", ignoreUnknownFields = false)
#Getter
#Setter
public class AppProperties {
private Boolean enabled;
private String url;
private String user;
}
I know I can set ignoreUnvalidFields = true in #ConfigurationProperties, but this is not exactly the behavior I want, since an empty value is valid in my case.
Is there a way I can ignore unbound properties? What can I do to avoid this problem?
UPDATE
After further debugging I can see that because ignoreUnkownFields = false in #ConfigurationPropertes then a NoUnboundElementsBindHandler is returned, which checks for unbound properties:
class ConfigurationPropertiesBinder {
private final ApplicationContext applicationContext;
private final PropertySources propertySources;
private final Validator configurationPropertiesValidator;
private final boolean jsr303Present;
private volatile Validator jsr303Validator;
private volatile Binder binder;
...
...
...
private BindHandler getBindHandler(ConfigurationProperties annotation,
List<Validator> validators) {
BindHandler handler = new IgnoreTopLevelConverterNotFoundBindHandler();
if (annotation.ignoreInvalidFields()) {
handler = new IgnoreErrorsBindHandler(handler);
}
if (!annotation.ignoreUnknownFields()) {
UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
handler = new NoUnboundElementsBindHandler(handler, filter);
}
if (!validators.isEmpty()) {
handler = new ValidationBindHandler(handler,
validators.toArray(new Validator[0]));
}
return handler;
}
}
Is there any way I can avoid this?
Related
I know this must be simple, and I've seen multiple similar questions, however my entire setup seems to be ok (as solutioned in the other posts), yet this problem persists.
Here's my setup
Environment
Spring Boot 2.6.3
Java 17
application.yml
platforms:
configs:
- platform: ABC
base-url: https://some-url-01.com/api
description:
logo:
- platform: DEF
base-url: https://some-url-02.com/api
description:
logo:
Config Properties
#Data
#ConstructorBinding
#ConfigurationProperties(prefix = "platforms")
public class PlatformProperties {
private final List<PlatformConfig> configs = new ArrayList<>();
#Data
public static class PlatformConfig {
private final Platform platform;
private final String baseUrl;
private final String description;
private final String logo;
}
}
Platform.java - a simple enum
public enum Platform {
ABC, DEF
}
Configuration
#Slf4j
#Configuration
#RequiredArgsConstructor
#EnableConfigurationProperties(PlatformProperties.class)
public class ClientConfig {
private final PlatformProperties platformProperties;
#PostConstruct
public void showProperties(){
platformProperties.getConfigs().forEach(System.out::println);
}
}
This entire setup seems perfectly fine (Ref: Spring Docs), however platformProperties.getConfigs() is always empty because there was no binding on platforms.configs as defined from the application.yml
I have a similar setup on a different project (springboot 2.5.7 / Java 8) where everything works exactly as expected.
What about this setup/configs is wrong???
Yah, I solved this a long time ago, just wanted to provide the answer, and it was quite simple too.
You see this line?
private final List<PlatformConfig> configs = new ArrayList<>();
That was the culprit.
Notice that the configs variable is final and was already assigned a new ArrayList<>() as it's value, hence it was immutable.
Solution was to remove the initial assignment so the line became;
private final List<PlatformConfig> configs;
The constructor binding went OK and the configs values were populated as expected.
I created a CustomSequenceGenerator for my model, everything is working fine.
Now i'm trying to read value from application.properties inside the CustomSequenceGenerator but failed.
I have tried many ways suggested by stackoverflow but still no luck on this.
1. Using #Value
2. Using Spring Environment env > env.getProperty()
3. Using #ConfigurationProperties
4. Using #PropertySource
Here is my codes:
Model
#Id
#GenericGenerator(name = "user_id_gen", strategy="com.my.model.common.CustomSequenceGenerator ",
parameters = {
#org.hibernate.annotations.Parameter(name = "sequence_name", value = "USER_SEQ")}
)
#GeneratedValue(generator = "user_id_gen")
#Column(name = "UserId", unique = true, nullable = false, length = 6)
private String userId;
CustomSequenceGenerator
public class CustomSequenceGenerator implements IdentifierGenerator, Configurable {
#Value("${seq.prefix}")
private String sequencePrefix;
.......
}
I put a break point on my CustomSequenceGenerator and i noticed that it jumps into the break point during server startup, so i guess that spring is not able to read the application.properties during startup/initialization.
application.properties
located in Resources/conf/application.properties, I have specify the location using -Dspring.config.location and other controllers have no problem in accessing the properties file, just CustomSequenceGenerator is having issue.
....
spring.jpa.show-sql=true
seq.prefix = MOCKDB.MOCK_SCHEMA.
....
So how can i read the properties value in this case ?
Thank you.
You can use System Properties to pass your value from application.properties
#Component
public class PropertiesConfig {
#Value("${z.prefix}")
private String zPrefix;
#PostConstruct
public void setProperty() {
System.setProperty("z.prefix", zPrefix);
}
}
And then read it in your custom generator
var zPrefix = System.getProperty("z.prefix");
In spring boot application, I define some config properties in yaml file as below.
my.app.maxAttempts = 10
my.app.backOffDelay = 500L
And an example bean
#ConfigurationProperties(prefix = "my.app")
public class ConfigProperties {
private int maxAttempts;
private long backOffDelay;
public int getMaxAttempts() {
return maxAttempts;
}
public void setMaxAttempts(int maxAttempts) {
this.maxAttempts = maxAttempts;
}
public void setBackOffDelay(long backOffDelay) {
this.backOffDelay = backOffDelay;
}
public long getBackOffDelay() {
return backOffDelay;
}
How can I inject the values of my.app.maxAttempts and my.app.backOffdelay to Spring Retry annotation? In the example below, I want to replace the value 10 of maxAttempts and 500Lof backoff value with the corresponding references of config properties.
#Retryable(maxAttempts=10, include=TimeoutException.class, backoff=#Backoff(value = 500L))
Staring from spring-retry-1.2.0 we can use configurable properties in #Retryable annotation.
Use "maxAttemptsExpression", Refer the below code for usage,
#Retryable(maxAttemptsExpression = "#{${my.app.maxAttempts}}",
backoff = #Backoff(delayExpression = "#{${my.app. backOffDelay}}"))
It will not work if you use any version less than 1.2.0.Also you don't require any configurable property classes.
You can also use existing beans in expression attributes.
#Retryable(include = RuntimeException.class,
maxAttemptsExpression = "#{#retryProperties.getMaxAttempts()}",
backoff = #Backoff(delayExpression = "#{#retryProperties.getBackOffInitialInterval()}",
maxDelayExpression = "#{#retryProperties.getBackOffMaxInterval" + "()}",
multiplierExpression = "#{#retryProperties.getBackOffIntervalMultiplier()}"))
String perform();
#Recover
String recover(RuntimeException exception);
where
retryProperties
is your bean which holds retry related properties as in your case.
You can use Spring EL as shown below to load the properties:
#Retryable(maxAttempts="${my.app.maxAttempts}",
include=TimeoutException.class,
backoff=#Backoff(value ="${my.app.backOffDelay}"))
I have a little SpringBoot Application, which can execute different functions via OpenLdap.
getUser
createUser
deleteUser
etc.
That works fine. Now i want to create an application.Yml, where i can manage different environments with different credentials. I read some tutorials, but i still have some understanding problems. Actually my code looks like that:
UserController:
...
protected static String serverURL = "xxxxx:90xx";
protected static String LdapBindDn = "cn=admin, xxxxx";
protected static String LdapPassword = "xxxx";
...
#RequestMapping(value = "/{userid:.+}",method = RequestMethod.GET,consumes="application/json",produces = "application/json")
public UserData getUser(#PathVariable String userid) {
DirContext context = connectToLdap();
//some operations...
context.close();
return user;
}
... // same for the other functions
My plan is now, that i want to specify the credentials in an extra application.yml instead of at the beginning of the UserController (see above).
Then i have created an application.yml in the src/main/resources:
# Actual environment
spring:
profiles.actives: development
---
# Dev Profile
spring:
profiles: dev
datasource:
serverUrl: ldaps://xxxxxx:90xx
AdminName: xxxx
AdminPassword: xxxxxx
BaseDN: xxxxx
---
# Production Profile
spring:
profiles: prod
datasource:
serverUrl: ldaps://xxxx2:90xx
AdminName: xxxxx2
AdminPassword: xxxxx2
BaseDN: xxxxxx
Now i need to call this configuration. I have read in one tutorial (http://therealdanvega.com/blog/2017/06/26/spring-boot-configuration-using-yaml) that i have to create an extra class "ApplicationProperties" for the properties of the .yml file.
#Component
#ConfigurationProperties("datasource")
public class ApplicationProperties {
private String serverURL;
private String adminName;
private String adminPassword;
private String baseDN;
// Getter-/Setter-Methods
}
Now i need to define my variables from the beginning with the values from the .yml, right? I went back to my UserController and tried something like that:
private String serverURL;
private String adminName;
private String adminPassword;
private String baseDN;
#Autowired
ApplicationProperties appProp;
#RequestMapping(value = "/{userid:.+}",method = RequestMethod.GET,consumes="application/json",produces = "application/json")
public UserData getUser(#PathVariable String userid) {
DirContext context = connectToLdap();
//some operations...
context.close();
return user;
}
... // same for the other functions
private DirContext connectToLdap(){
System.out.prinln(appProp.getServerURL());
System.out.prinln(appProp.getAdminName());
System.out.prinln(appProp.getAdminPassword());
.... // Code for the Ldap connection
}
But the variable "appProp" is still empty. I know, that here is somewhere a big understanding problem. I don't know how to call these properties from the .yml file.
Thanks for every help in advance!
You can get properties from your .yml file by creating a config class:
application.yml
datasource:
serverUrl: ldaps://xxxxxx:90xx
adminName: xxxx
adminPassword: xxxxxx
baseDN: xxxxx
ApplicationConfig.java class :
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "datasource")
public class ApplicationConfig {
private String serverUrl;
private String adminName;
private String adminPassword;
private String baseDN;
//getters setters
}
Then you can call your ApplicationConfig class
#Autowired
public ApplicationConfig app;
public UserData getUser(#PathVariable String userid) {
DirContext context = connectToLdap();
//some operations...
appProp.getServerUrl();
appProp.getAdminName();
context.close();
return user;
}
And I recommend you to create profile based properties as spring boot picks them automatically, like application-{profile}.{properties|yml}
You can create application-production.yml file and set your profile by adding #Profile("production") annotation in your class.
In spring boot application, I define some config properties in yaml file as below.
my.app.maxAttempts = 10
my.app.backOffDelay = 500L
And an example bean
#ConfigurationProperties(prefix = "my.app")
public class ConfigProperties {
private int maxAttempts;
private long backOffDelay;
public int getMaxAttempts() {
return maxAttempts;
}
public void setMaxAttempts(int maxAttempts) {
this.maxAttempts = maxAttempts;
}
public void setBackOffDelay(long backOffDelay) {
this.backOffDelay = backOffDelay;
}
public long getBackOffDelay() {
return backOffDelay;
}
How can I inject the values of my.app.maxAttempts and my.app.backOffdelay to Spring Retry annotation? In the example below, I want to replace the value 10 of maxAttempts and 500Lof backoff value with the corresponding references of config properties.
#Retryable(maxAttempts=10, include=TimeoutException.class, backoff=#Backoff(value = 500L))
Staring from spring-retry-1.2.0 we can use configurable properties in #Retryable annotation.
Use "maxAttemptsExpression", Refer the below code for usage,
#Retryable(maxAttemptsExpression = "#{${my.app.maxAttempts}}",
backoff = #Backoff(delayExpression = "#{${my.app. backOffDelay}}"))
It will not work if you use any version less than 1.2.0.Also you don't require any configurable property classes.
You can also use existing beans in expression attributes.
#Retryable(include = RuntimeException.class,
maxAttemptsExpression = "#{#retryProperties.getMaxAttempts()}",
backoff = #Backoff(delayExpression = "#{#retryProperties.getBackOffInitialInterval()}",
maxDelayExpression = "#{#retryProperties.getBackOffMaxInterval" + "()}",
multiplierExpression = "#{#retryProperties.getBackOffIntervalMultiplier()}"))
String perform();
#Recover
String recover(RuntimeException exception);
where
retryProperties
is your bean which holds retry related properties as in your case.
You can use Spring EL as shown below to load the properties:
#Retryable(maxAttempts="${my.app.maxAttempts}",
include=TimeoutException.class,
backoff=#Backoff(value ="${my.app.backOffDelay}"))