#ConfigurationProperties prefix not working - java

.yml file
cassandra:
keyspaceApp:junit
solr:
keyspaceApp:xyz
Bean
#Component
#ConfigurationProperties(prefix="cassandra")
public class CassandraClientNew {
#Value("${keyspaceApp:#null}") private String keyspaceApp;
Main method file
#EnableAutoConfiguration
#ComponentScan
#PropertySource("application.yml")
public class CommonDataApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(CommonDataApplication.class)
.web(false).headless(true).main(CommonDataApplication.class).run(args);
}
}
TestCase
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = CommonDataApplication.class)
#IntegrationTest
#EnableConfigurationProperties
public class CassandraClientTest {
#Autowired
CassandraClientNew cassandraClientNew;
#Test
public void test(){
cassandraClientNew.getSession();
System.out.println(" **** done ****");
}
}
Instead of setting junit as the keyspaceApp it sets xyz.
Looks like prefix="cassandra" not working

It looks like you are trying to use Spring Boot Typesafe Configuration Properties feature.
So in order to have it working correctly, you have to add a few changes to your code:
First of all, your CommonDataApplication class should have #EnableConfigurationProperties annotation e.g.
#EnableAutoConfiguration
#ComponentScan
#PropertySource("application.yml")
#EnableConfigurationProperties
public class CommonDataApplication {
public static void main(String[] args) {
// ...
}
}
I do not believe you need #PropertySource("application.yml") annotation as application.yml (as well as application.properties and application.xml) is a default configuration file used by Spring Boot.
Your CassandraClientNew class does not need to have #Value annotation prefixing keyspaceApp property. And your keyspaceApp has to have a setter method.
#Component
#ConfigurationProperties(prefix="cassandra")
public class CassandraClientNew {
private String keyspaceApp;
public void setKeyspaceApp(final String keyspaceApp) {
this.keyspaceApp = keyspaceApp;
}
}
BTW, if you are using List's or Sets and you initialise collections (e.g. List<String> values = new ArrayList<>();), then only getter is required. If a collection is not initialised then you need to provide a setter method too (otherwise an exception will be thrown).
I hope that will help.

General answer
1. In your properties file (application.properties or application.yml)
# In application.yaml
a:
b:
c: some_string
2. Declare your class:
#Component
#ConfigurationProperties(prefix = "a", ignoreUnknownFiels = false)
public class MyClassA {
public MyClassB theB; // This name actually does not mean anything
// It can be anything
public void setTheB(MyClassB theB) {
this.theB = theB;
}
public static MyClassB {
public String theC;
public void setTheC(String theC) {
this.theC = theC;
}
}
}
3. Declare public setters! And this is crucial!
Make sure to have these public methods declared in the above classes. Make sure they have "public" modifier.
// In MyClassA
public void setTheB(MyClassB theB) {
this.theB = theB;
}
// In MyClassB
public void setTheC(String theC) {
this.theC = theC;
}
That's it.
Final notes
The property names in your classes do not mean anything to Spring. It only uses public setters. I declared them public not to declare public getters here. Your properties may have any access modifiers.
Pay attention to the attribute "ignoreUnknownFields". Its default value is "true". When it is "false" it will throw exception if any of your properties in file "application.yml" was not bound to any class property. It will help you a lot during debugging.

I don't know where the "xyz" came from (maybe you aren't showing your whole application.yml?). You don't normally bind with #Value in #ConfigurationProperties though (it has no way of knowing what your prefix is). Did you actually #EnableCongigurationProperties anywhere? Are you using SpringApplication to create the application context?

Just add a public setter or #Setter from Lombok.

I had a similar but different issue compared to OP, and couldn't find an answer anywhere, so I'll post about it. Let say you're trying to use Lombok to go and auto generate the getters & setters & other fields for you in your configuration file.
Here is an example:
#Configuration
#ConfigurationProperties(prefix = "address")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class AddressConfig {
private Endpoint endpoint;
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public static class Endpoint {
private String url;
}
}
If using lombok #Getter & #Setter annotations (or #Data which also takes care of #Getter & #Setter), then make sure to also have this annotation:
#Accessors(fluent = false)
The reason: spring needs the setter methods to actually populate the configuration for you. However, in the above example, here's how the url would be set internally in spring:
addressConfig.getEndpoint().setUrl("...");
You can see that getters are prefixed with get and setters are prefixed with set.
If you do NOT have #Accessors(fluent = false) set, then the setters will use the fluent style of accessors, which don’t prepend getters with the word get and setters with the word set. This breaks springs ability to populate configuration pojos properly.
Spring will not tell you this when you execute the application. You only get null pointer exceptions when trying when your application tries to use those configuration variables. I only realized I need to put an #Accessors(fluent = false) annotation after hours of trial & error since I couldn’t find the answer on google or stackoverflow.
side note: intellij will warn you that false is the default value for #Accessors. This is wrong -_-
Anyways, here's the configuration class with the annotation (yes, you only need to add this at the top of the class, just one time, and not in the inner classes).
#Configuration
#ConfigurationProperties(prefix = "address")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class AddressConfig {
private Endpoint endpoint;
#Data
#NoArgsConstructor
#AllArgsConstructor
#Accessors(fluent = false)
#Builder
public static class Endpoint {
private String url;
}
}

That's my FULL solution for this case:
My Class will be receiving the properties:
// Setter are CRUCIAL for PropertySource + ConfigurationProperties works properly
#PropertySource(value = "classpath:application-dev.yml", ignoreResourceNotFound = true)
#ConfigurationProperties(prefix = "spring.data.mongodb")
#Setter
#Profile("dev")
#Configuration
#EnableReactiveMongoRepositories(
basePackages = {
"com.webflux.mongo2.project.repo",
"com.webflux.mongo2.task.repo"})
public class MyClassGettingTheProperties {
private String database;
private String host;
private String port;
private String username;
private String password;
private String authenticationDatabase;
}
My property file - application-dev.yml:
spring:
data:
mongodb:
database: ${MONGO_INITDB_DATABASE}
host: ${HOST}
port: ${PORT}
username: ${MONGO_INITDB_ROOT_USERNAME}
password: ${MONGO_INITDB_ROOT_PASSWORD}
authentication-database: ${AUTH_MAN}
My docker-compose which is the "origin" of the properties:
version: "3.4"
x-common-variables:
&env-vars
PORT_API: 1313
MONGO_INITDB_DATABASE: zzzzz
HOST: yyyyy
PORT: 27017
MONGO_INITDB_ROOT_USERNAME: xxxxx
MONGO_INITDB_ROOT_PASSWORD: xxxxxxx
AUTH_MAN: kkkkk
volumes:
api_vol:
networks:
mongo-cluster:
services:
api-db:
container_name: api-db
image: mongo:4.4.4
restart: always
ports:
- "27017:27017"
networks:
- mongo-cluster
volumes:
- api_vol:/data/db
environment:
*env-vars

Related

ConfigurationProperties returns NULL if the object is override in config file

I have ConfigurationProperties setup in spring-boot application as follow:
#ConfigurationProperties(prefix = "config")
public class TestConfiguration {
private List<IndexMappings> indexmappings = new ArrayList<>();
#Data
#NoArgsConstructor
#AllArgsConstructor
#Validated
public static class IndexMappings {
private String user;
//Default set to FALSE
private Boolean checkFlag = false;
}
}
If I add all this in application.yml everything works well. Like following:
config:
indexmappings:
- user: test-1
checkFlag: false
- user: test-2
checkFlag: true
But now if I try to override user test-2 in application-override.yml file as follow:
config:
indexmappings:
- user: test-2
checkFlag: true
I'm getting null for test-1 as the object is changed now. How to avoid this scenario? I want to only override test-2.
Help will be appreciated!

SpringBoot 2.6.3 not binding #ConfigurationProperties on List of Objects

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.

Reason: The elements were left unbound

How can I load List of objects from configuration yml file in java springboot application?
I already tried several sources:
configuration-properties-in-spring-boot
spring-boot-configurationproperties-example
SpringBoot 2 the elements were left unbound
Stack: Java 11, SpringBoot 2.1.4, Lombok, configuration file in .yml format.
I tried to implement simple #Component, which will load data from configuration file.
Configuration values are:
allowed:
- first-crossroad: ONE
second-crossroad: TWO
third-crossroad: TWO
fourth-crossroad: THREE
- first-crossroad: ONE
second-crossroad: THREE
third-crossroad: TWO
fourth-crossroad: ONE
Java class for data loading is:
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "")
#Data
public class AllowedCrossroads {
private List<CrossroadCombination> allowed;
}
Java CrossroadCombination object definition:
#Data
#Builder
#AllArgsConstructor
public class CrossroadCombination {
private String firstCrossroad;
private String secondCrossroad;
private String thirdCrossroad;
private String fourthCrossroad;
}
I expected values to be loaded during application run. But I am getting error:
Property: allowed[0].first-crossroad
Value: ONE
Origin: class path resource [application.yml]:644:17
Reason: The elements [allowed[0].first-crossroad,allowed[0].fourth-crossroad,allowed[0].second-crossroad,allowed[0].third-crossroad,allowed[1].first-crossroad,allowed[1].fourth-crossroad,allowed[1].second-crossroad,allowed[1].third-crossroad,allowed[2].first-crossroad,allowed[2].fourth-crossroad,allowed[2].second-crossroad,allowed[2].third-crossroad] were left unbound.
(One of many) Solution:
add: #NoArgsConstructor
to java class:
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class CrossroadCombination {
private String firstCrossroad;
private String secondCrossroad;
private String thirdCrossroad;
private String fourthCrossroad;
}
Explanation:
It is because when we create an object, we need to have an empty object and then fill it with data. This is why we need no args constructor.
Anyway solution from "#Daniel V" is also correct and thanks for that one!
Making The Inner class as static will work
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "")
#Data
public class AllowedCrossroads {
private List<CrossroadCombination> allowed;
#Data
public static class CrossroadCombination {
private String firstCrossroad;
private String secondCrossroad;
private String thirdCrossroad;
private String fourthCrossroad;
}
}
including Getters and Setters for all fields solved the issue
Did you try with java ResourceBundle library?
for example:
ResourceBundle configApp = ResourceBundle.getBundle("path to your configuration file");
after that you can use configApp like so:
configApp.getString("first-crossroad");
can you try that and tell me how it goes.
EDIT:
The data in your configuration file if your planning to use ResourceBundle should look like this:
allowed=[first-crossroad= ONE, second-crossroad= TWO, third-crossroad= TWO,fourth-crossroad= THREE,first-crossroad= ONE, second-crossroad= THREE,third-crossroad= TWO, fourth-crossroad= ONE]
the "[]" isn't neccesary but it makes it look more readable
then you call it like this:
configApp.getObject("allowed");
that will store it like this:
[first-crossroad= ONE, second-crossroad= TWO, third-crossroad= TWO,fourth-crossroad= THREE,first-crossroad= ONE, second-crossroad= THREE,third-crossroad= TWO, fourth-crossroad= ONE]
then you can cast this to an array or play with it like this.
In my case the variable name is not matched. Please check all fields names and mapping configuration key names.

How to set tableName dynamically using environment variable in spring boot?

I am using AWS ECS to host my application and using DynamoDB for all database operations. So I'll have same database with different table names for different environments. Such as "dev_users" (for Dev env), "test_users" (for Test env), etc.. (This is how our company uses same Dynamo account for different environments)
So I would like to change the "tableName" of the model class using the environment variable passed through "AWS ECS task definition" environment parameters.
For Example.
My Model Class is:
#DynamoDBTable(tableName = "dev_users")
public class User {
Now I need to replace the "dev" with "test" when I deploy my container in test environment. I know I can use
#Value("${DOCKER_ENV:dev}")
to access environment variables. But I'm not sure how to use variables outside the class. Is there any way that I can use the docker env variable to select my table prefix?
My Intent is to use like this:
I know this not possible like this. But is there any other way or work around for this?
Edit 1:
I am working on the Rahul's answer and facing some issues. Before writing the issues, I'll explain the process I followed.
Process:
I have created the beans in my config class (com.myapp.users.config).
As I don't have repositories, I have given my Model class package name as "basePackage" path. (Please check the image)
For 1) I have replaced the "table name over-rider bean injection" to avoid the error.
For 2) I printed the name that is passing on to this method. But it is Null. So checking all the possible ways to pass the value here.
Check the image for error:
I haven't changed anything in my user model class as beans will replace the name of the DynamoDBTable when the beans got executed. But the table name over riding is happening. Data is pulling from the table name given at the Model Class level only.
What I am missing here?
The table names can be altered via an altered DynamoDBMapperConfig bean.
For your case where you have to Prefix each table with a literal, you can add the bean as such. Here the prefix can be the environment name in your case.
#Bean
public TableNameOverride tableNameOverrider() {
String prefix = ... // Use #Value to inject values via Spring or use any logic to define the table prefix
return TableNameOverride.withTableNamePrefix(prefix);
}
For more details check out the complete details here:
https://github.com/derjust/spring-data-dynamodb/wiki/Alter-table-name-during-runtime
I am able to achieve table names prefixed with active profile name.
First added TableNameResolver class as below,
#Component
public class TableNameResolver extends DynamoDBMapperConfig.DefaultTableNameResolver {
private String envProfile;
public TableNameResolver() {}
public TableNameResolver(String envProfile) {
this.envProfile=envProfile;
}
#Override
public String getTableName(Class<?> clazz, DynamoDBMapperConfig config) {
String stageName = envProfile.concat("_");
String rawTableName = super.getTableName(clazz, config);
return stageName.concat(rawTableName);
}
}
Then i setup DynamoDBMapper bean as below,
#Bean
#Primary
public DynamoDBMapper dynamoDBMapper(AmazonDynamoDB amazonDynamoDB) {
DynamoDBMapper mapper = new DynamoDBMapper(amazonDynamoDB,new DynamoDBMapperConfig.Builder().withTableNameResolver(new TableNameResolver(envProfile)).build());
return mapper;
}
Added variable envProfile which is an active profile property value accessed from application.properties file.
#Value("${spring.profiles.active}")
private String envProfile;
We have the same issue with regards to the need to change table names during runtime. We are using Spring-data-dynamodb 5.0.2 and the following configuration seems to provide the solutions that we need.
First I annotated my bean accessor
#EnableDynamoDBRepositories(dynamoDBMapperConfigRef = "getDynamoDBMapperConfig", basePackages = "my.company.base.package")
I also setup an environment variable called ENV_PREFIX which is Spring wired via SpEL.
#Value("#{systemProperties['ENV_PREFIX']}")
private String envPrefix;
Then I setup a TableNameOverride bean:
#Bean
public DynamoDBMapperConfig.TableNameOverride getTableNameOverride() {
return DynamoDBMapperConfig.TableNameOverride.withTableNamePrefix(envPrefix);
}
Finally, I setup the DynamoDBMapperConfig bean using TableNameOverride injection. In 5.0.2, we had to setup a standard DynamoDBTypeConverterFactory in the DynamoDBMapperConfig builder to avoid NPE.:
#Bean
public DynamoDBMapperConfig getDynamoDBMapperConfig(DynamoDBMapperConfig.TableNameOverride tableNameOverride) {
DynamoDBMapperConfig.Builder builder = new DynamoDBMapperConfig.Builder();
builder.setTableNameOverride(tableNameOverride);
builder.setTypeConverterFactory(DynamoDBTypeConverterFactory.standard());
return builder.build();
}
In hind sight, I could have setup a DynamoDBTypeConverterFactory bean that returns a standard DynamoDBTypeConverterFactory and inject that into the getDynamoDBMapperConfig() method using the DynamoDBMapperConfig builder. But this will also do the job.
I up voted the other answer but here is an idea:
Create a base class with all your user details:
#MappedSuperclass
public abstract class AbstractUser {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
Create 2 implentations with different table names and spirng profiles:
#Profile(value= {"dev","default"})
#Entity(name = "dev_user")
public class DevUser extends AbstractUser {
}
#Profile(value= {"prod"})
#Entity(name = "prod_user")
public class ProdUser extends AbstractUser {
}
Create a single JPA respository that uses the mapped super classs
public interface UserRepository extends CrudRepository<AbstractUser, Long> {
}
Then switch the implentation with the spring profile
#RunWith(SpringJUnit4ClassRunner.class)
#DataJpaTest
#Transactional
public class UserRepositoryTest {
#Autowired
protected DataSource dataSource;
#BeforeClass
public static void setUp() {
System.setProperty("spring.profiles.active", "prod");
}
#Test
public void test1() throws Exception {
DatabaseMetaData metaData = dataSource.getConnection().getMetaData();
ResultSet tables = metaData.getTables(null, null, "PROD_USER", new String[] { "TABLE" });
tables.next();
assertEquals("PROD_USER", tables.getString("TABLE_NAME"));
}
}

Get application.yml infos in spring

I have add some config inside my application.yml file and I want to read it from my Java code.
The added node inside the YAML file looks like this:
myConfig:
projectOne:
mantisID: 501
user: username
password: passwd
projectTwo:
mantisID: 502
user: username
password: passwd
What I want is to get a List of Project objects where
Project.mantisID = 501,
Project.user = "username",
Project.password = "passwd",
etc...
I know spring can read this file with some #Value annotation but how can I use this in order to get what I need?
You can use #ConfigurationProperties annotation to map your configuration to a Bean, then you'll be able to inject your Bean anywhere and fetch those properties.
To do so, first create a class which represents the data structure in your configuration. Then annotate it with #ConfigurationProperties and #Configuration annotations.
#Configuration
#ConfigurationProperties
public class MyConfig {
private final Map<String, Project> myConfig = new HashMap<>();
public Map<String, Project> getMyConfig() {
return myConfig;
}
public static class Project {
private String mantisID;
private String password;
private String user;
// Getters and setters...
}
}
Note that getters and setters are required in the Project class. Also keep in mind that naming of getters and setters is important here.
After you have setup this class, you can inject it anywhere in your project and access its properties.
#Service
public class SomeService {
private final Map<String, MyConfig.Project> projects;
#Autowired
public SomeService(MyConfig config) {
this.projects = config.getMyConfig();
projects.get("projectOne").getMantisID();
projects.get("projectTwo").getPassword();
}
}
You can read more about this here.
Just to finish, I answered myself to my second question.
This is what my service looks like now :
#Service
public class MantisProjectService {
private final Map<String, MantisProjectConfiguration.Project> projects;
private List<MantisProjectConfiguration.Project> mantisProjects = new ArrayList<>();
#Autowired
public MantisProjectService(MantisProjectConfiguration mantisProjectConfiguration)
{
this.projects = mantisProjectConfiguration.getMantisProjectConfiguration();
for (Map.Entry<String, MantisProjectConfiguration.Project> project : projects.entrySet())
{
MantisProjectConfiguration.Project mantisProject = project.getValue();
mantisProject.setName(project.getKey());
mantisProjects.add(mantisProject);
}
}
public List<MantisProjectConfiguration.Project> getMantisProjects()
{
return mantisProjects;
}
}
It returns a List of all the projects. And it is awesome! =)

Categories