How to skip bind properties per profile in spring boot(#ConfigurationProperties, #ConstructorBinding) - spring

I want to optionally bind a field to specific class for each profile
the example code is as follows ...
spring:
config:
activate:
on-profile: test1
app:
cash:
conn:
connection-timeout: 1000
response-timeout: 2000
...
---
spring:
config:
activate:
on-profile: test2
#Getter
#Validated
#ConstructorBinding
#ConfigurationProperties(value = "app.cash.conn")
#RequiredArgsConstructor
public class CashBoxConnectionProperties {
#NotNull
#Positive
private final Integer connectionTimeout;
#NotNull
#Positive
private final Integer responseTimeout;
#NotNull
#PositiveOrZero
private final Integer retryMaxAttempts;
#NotNull
#Positive
private final Integer retryMaxDelay;
}
When running as test1 profile, the application runs normally because the properties value is set, but when running as test2 profile, the error 'Binding to target...' occurs because there is no app.cash.conn properties.
The CashBoxConnectionProperties is not required in test2 profile, so is there any other way than to remove #NotNull annotation?

You can annotate the CashBoxConnectionProperties class with #Profile("test2"), which makes it only available when the test2 profile is active. To make it work, the class should be marked as #Component, since #Profile is only applicable to Spring beans.
For more details check the documentation.

You can use validation groups, to turn off validation for specific fields.
I never used this myself, but you can find an explanation here: https://www.baeldung.com/javax-validation-groups

Related

Cannot bind Map to Object in Spring Configuration Properties from YAML file

I have the following configuration in my Spring boot's application.yml file:
project:
country-properties:
france:
capital: paris
population: 60
And I have the the properties class : CountryProperties :
#Getter
#AllArgsConstructor
#ConstructorBinding
#ConfigurationProperties(prefix="project.country-properties")
public class CountryProperties {
private Map<String, CountryData> countryProperties;
#Getter
#Setter
public static class CountryData {
private String capital;
private Integer population;
}
However my CountryProperties is always null, and it's because of a failed mapping with the CountryData object.
Any ideas what is wrong with what I wrote?
You have the annotation #ConstructorBinding. This annotation tells Spring to look for a constructor in your class that has parameters corresponding to your configuration properties, and then will bind the properties.
What you are missing is:
public CountryProperties(Map<String, CountryData> countryProperties) {
this.countryProperties = countryProperties;
}
Update:
After inspecting your code again, it looks like you aren't mapping the configuration correctly to the instance field. Please update your #ConfigurationProperties(prefix="project.country-properties") to #ConfigurationProperties(prefix="project").
Also replace the #ConstructorBinding with #Configuration.

validate ConfigurationProperties mapping

Is there a way to validate application.properties (or yml) if the properties match Java bean that it is mapped to via #ConfigurationProperties - so that if there is a typo in an attribute, exception will be thrown?
I tried using #Validated but it works only if every property has #NotNull annotation - but this is not exactly what I want to achieve... there may be some nullable properties in the config and I still want to "validate" them
I just spent 2 hours debugging an issue and I found out, the problem is that I misspelled an attribute name
e.g. application.yml
property1: 1
properrrrrty2: 2
#Configuration
#ConfigurationProperties
public class AppConfig {
private String property1;
private String property2; // <--- this property does not match due to typo in application.yml
}
A)
If you want to be sure that always a property exists then use #Validated with #NotNull for that property. #NotNull will complain if it does not find that property. You can still have the property there with an empty value if that is what you mean with nullable properties and NotNull will not complain.
You can't say I want it to be able to be nullable but also the validator should complain when that property is null.
So to sum things up.
#NotEmpty property must exist and also not have an empty value
#NotNull property must just exist. It does not care if it exists with an empty value.
That's why I insist you go with NotNull for your requirements.
B)
Also I can think of another way to handle that.
#Component
public class AppConfig {
#Value("${property1}")
private String property1;
#Value("${property2}")
private String property2;
}
Using injection with #Value, spring will fail to initialize the singleton AppConfig during application startup if some property with exactly the same name does not exist on properties file, therefore you will be informed that no property with that name exists and the application will not start up.
You can specify ignoreUnknownFields = false to ensure that no unknown properties are defined under the corresponding prefix. (docs):
Flag to indicate that when binding to this object unknown fields should be ignored. An unknown field could be a sign of a mistake in the Properties.
Borrowing from your example:
#Configuration
#ConfigurationProperties(prefix = "myapp", ignoreUnknownFields = false)
public class AppConfig {
private String property1;
private String property2;
}
This means myapp.property1 and myapp.property2 are allowed but not required to be set, so they remain nullable.
Any other set property with the myapp prefix (such as myapp.properrrrrty2=2) will cause a startup failure and the offending property name will be logged in the exception.

Hibernate collection with #ElementCollection contains duplicated elements in database

Have a class.
#Entity
#Table(name="sessions")
#Component
public class Session {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ElementCollection
private Set<String> addedWords;
}
My flow is: get session one time. Add one word to addedWords and save session, repeat it. The problem that when I check my database, there are a lot of duplications, at the same time I don't have any duplications in Java class. So the Set<String> addedWords in class and this words in database not the same. Why I have this strange behavior and how to make things work well without any duplication? I use PostgreSQL. Saving method:
public synchronized void addWord(Session session, String word) {
session.getAddedWords().add(word);
sessionRepository.save(session);
}
Here spring config
spring:
jpa:
show-sql: true
hibernate:
ddl-auto: update

Spring does not complain if a property is not set when using ConfigurationProperties

I have a bean that is configured via ConfigurationProperties:
#Component
#ConfigurationProperties(prefix = "mybean")
public class MyBean {
#NotEmpty
private String name;
// Getters, setters, ...
}
I configure the field values via application.yml but in "two levels". In the default application.yml I just set the value to the value of another property:
myBean.name: ${theValueOf.myBean.name}
In the profile specific YML file I have:
theValueOf.myBean.name: 'The desired value'
My expectation would be that if I forget to specify the property theValueOf.myBean.name then the application should fail at startup with the message that the placeholder 'theValueOf.myBean.name' could not be resolved. Instead, the field name is assigned the value (literally) ${theValueOf.myBean.name}.
If I annotate the name field with #Value("${myBean.name}") (and do not use ConfigurationProperties), and forget to define the property theValueOf.myBean.name, then the application fails at startup -- as expected.
My question is: How can I make Spring fail at startup with the message 'Could not resolve placeholder ...' when using ConfigurationProperties?
Simply mark your properties with JSR303 annotations, inside your #ConfigurationProperties.
#Component
#ConfigurationProperties(prefix = "mybean")
public class MyBean {
#NotEmpty
private String name;
}

How to Access Spring Application Configuration Values based on Requests?

In one of my Spring Boot application, I have a controller that needs to read from application.yml for accessing an external API. I have organization setup in the external API, similar to github organization, and each organization comes with its own client ID and secret key.
My application.yml looks something like this.
organization:
abc:
client:
clientId: f29e347add73
clientSecret: dad2404e63ec4cd
xyz:
client:
clientId: 0884340cf3e793
clientSecret: a26ff0119d907e9
Currently, I can pick up a property value in my controller like this.
#Value("${organization.abc.client.clientId}")
private String abcClientId;
#Value("${organization.abc.client.clientSecret}")
private String abcClientSecret;
But what I need to do is, instead of hardcoding, if a request for abc comes, the configuration for abc is picked up and when for xyz comes, the configuration for xyz is picked up. Same for any number of the organization I keep adding to the application.yml file.
Please help me on how to achieve this.
If you can rewrite your applicaiotn.yml as follows, you can read it into a list of object with#ConfigurationProperties.
organization:
list:
-
name: abc
client:
clientId: f29e347add73
clientSecret: dad2404e63ec4cd
-
name: xyz
client:
clientId: 0884340cf3e793
clientSecret: a26ff0119d907e9
Create a class to map properties to list of object:
#Service
#ConfigurationProperties(prefix="organization")
public class ConfigurationService {
private List<Org> list = new ArrayList<>();
//getters and setters
public static class Org {
private String name;
private Client client;
//getters and setters
}
public static class Client {
private String clientId;
private String clientSecret;
//getter and setter
}
}
Now you can access this list like...
#Autowired
ConfigurationService configurationService;
You can inject an Environment (initialized by default by Spring Boot) like this:
#Autowired
private Environment env;
And then use it like:
env.getProperty("my-property")

Resources