Spring @ConfigurationProperties annotation explained
Tags: spring, microservices, java, annotations
Published: 2020-03-01 10:37:35

{% medium https://medium.com/@habeebcycle/spring-configurationproperties-14c684bd2636 %}

Spring provides several ways of injecting/looking up values from the application configuration properties file. 

One of them is the <code>**@Value**</code> annotation discussed in the <a href='https://medium.com/@habeebcycle/spring-value-annotation-tricks-be3f12f3e804' target='_blank'>Spring @Value annotation tricks</a> write up. 

Another one is the using <code>**@ConfigurationProperties**</code> annotation on a configuration bean to inject properties values to a bean and use it in the class. Also is the using of <code>**Environment**</code> object to lookup values, properties or profiles in the classpath.

In this write-up, I will demonstrate how to use <code>**@ConfigurationProperties**</code> annotation to inject properties values from the <code>application.properties</code> or <code>application.yaml</code> files.

Let go to https://start.spring.io/ to generate and bootstrap our project.
Choose Maven Project, Java as the language, give your project the group name and artefact Id. Select <code>**Spring Web**</code> as the only dependency for our project.


<img src='https://miro.medium.com/max/1271/1*toU8F6AqAbrd8OTUukNiTQ.png'/>

Click ‘Generate’ button to download the bootstrapped project as a zip file. Unzip the file and open it with your prefered IDE as a Maven project to download all the required dependencies and at the end of all these, your project structure should look like the image below:

<img src='https://miro.medium.com/max/484/1*5hWe9Lm7VVjPecvb28zfBA.png'/>

Open the application.properties file and write the properties values below:

```properties
#DEV environment properties
dev.name=Development Application
dev.port=8091
dev.dbtype=Maria DB
dev.version=1.0.alpha
dev.dburl=jdbc:mariadb://localhost:3306/
dev.dbname=studentDB
dev.dbuser=root
dev.dbpassword=root

#QA environment properties
qa.name=QA Test Application
qa.port=8092
qa.dbtype=Mongo DB
qa.version=1.2.beta
qa.dburl=mongodb://mongodb0.example.com:27017/
qa.dbname=studentDB
qa.dbuser=admin
qa.dbpassword=admin

#PROD environment properties
prod.name=Production Application
prod.port=8091
prod.dbtype=MySQL
prod.version=1.0.1
prod.dburl=jdbc:mysql://localhost:3308/
prod.dbname=studentDB
prod.dbuser=admin-prod
prod.dbpassword=admin-prod
```

The file contains three properties values for three different environment — dev, qa and prod. Each property is prefixed with the type of environment they belong to. This is not a good practice to have all these specific environment properties on a single file. In reality, we cannot have different environment properties like this, but rather create a specific application properties file for each environment, set up a <code>**profile**</code> and choose the active profile or profiles to use. Just bear with me with this approach to show how to use the annotation. Our aim is to load these properties into our application by creating beans and using <code>**@ConfigurationProperties**</code> annotation.

Let’s create a package called config and create three classes <code>**DevConfigProps.java, QaConfigProps.java** and **ProdConfigProps.java**</code> in this package and annotate each class with <code>**@Configuration**</code> to tell spring that these classes should be scanned, managed and configured as spring beans during application bootup.

<img src='https://miro.medium.com/max/490/1*yIrRHppySJTkjA0fXWEJkg.png' />

If you consider the <code>application.properties</code> file above, you will notice that each of these properties is either prefix with <code>**dev**</code>, <code>**qa**</code> or <code>**prod**</code>. These prefixes will be used to configure these classes by using the <code>**@ConfigurationProperties("prefix")**</code> annotation.

<code>**@ConfigurationProperties("dev")**</code> for dev properties, <code>**@ConfigurationProperties("qa")**</code> for qa properties and <code>**@ConfigurationProperties("prod")**</code> for prod properties.

```java
//DevConfigProps.java
package com.habeebcycle.configurationpropertiesannotation.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties("dev")
public class DevConfigProps {
    
}


//QaConfigProps.java

package com.habeebcycle.configurationpropertiesannotation.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties("qa")
public class QaConfigProps {
    
}


//ProdConfigProps.java

package com.habeebcycle.configurationpropertiesannotation.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties("prod")
public class ProdConfigProps {
    
}
```

<code>**@ConfigurationProperties**</code> allows us to map the entire properties or YAML files into an object easily. It also allows us to validate properties with JSR-303 bean validation. By default, the annotation reads from the <code>**application.properties**</code> file. The properties file to be used can be changed with <code>**@PropertySource("file-name")**</code> annotation if we don’t want to use the default properties file.

The next step is to create all the properties names as a variable with their setters and getters.

for <code>**DevConfigProps.java**</code>

```java
package com.habeebcycle.configurationpropertiesannotation.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties("dev")
public class DevConfigProps {

    private String name;
    private int port;
    private String dbType;
    private String version;
    private String dbUrl;
    private String dbName;
    private String dbUser;
    private String dbPassword;

    public String getName() {
        return name;
    }

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

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getDbType() {
        return dbType;
    }

    public void setDbType(String dbType) {
        this.dbType = dbType;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getDbUrl() {
        return dbUrl;
    }

    public void setDbUrl(String dbUrl) {
        this.dbUrl = dbUrl;
    }

    public String getDbName() {
        return dbName;
    }

    public void setDbName(String dbName) {
        this.dbName = dbName;
    }

    public String getDbUser() {
        return dbUser;
    }

    public void setDbUser(String dbUser) {
        this.dbUser = dbUser;
    }

    public String getDbPassword() {
        return dbPassword;
    }

    public void setDbPassword(String dbPassword) {
        this.dbPassword = dbPassword;
    }
}
```

for <code>**QaConfigProps.java**</code>
```java
package com.habeebcycle.configurationpropertiesannotation.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties("qa")
public class QaConfigProps {

    private String name;
    private int port;
    private String dbType;
    private String version;
    private String dbUrl;
    private String dbName;
    private String dbUser;
    private String dbPassword;

    public String getName() {
        return name;
    }

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

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getDbType() {
        return dbType;
    }

    public void setDbType(String dbType) {
        this.dbType = dbType;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getDbUrl() {
        return dbUrl;
    }

    public void setDbUrl(String dbUrl) {
        this.dbUrl = dbUrl;
    }

    public String getDbName() {
        return dbName;
    }

    public void setDbName(String dbName) {
        this.dbName = dbName;
    }

    public String getDbUser() {
        return dbUser;
    }

    public void setDbUser(String dbUser) {
        this.dbUser = dbUser;
    }

    public String getDbPassword() {
        return dbPassword;
    }

    public void setDbPassword(String dbPassword) {
        this.dbPassword = dbPassword;
    }
}
```

for <code>**ProdConfigProps.java**</code>
```java
package com.habeebcycle.configurationpropertiesannotation.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties("prod")
public class ProdConfigProps {

    private String name;
    private int port;
    private String dbType;
    private String version;
    private String dbUrl;
    private String dbName;
    private String dbUser;
    private String dbPassword;

    public String getName() {
        return name;
    }

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

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getDbType() {
        return dbType;
    }

    public void setDbType(String dbType) {
        this.dbType = dbType;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getDbUrl() {
        return dbUrl;
    }

    public void setDbUrl(String dbUrl) {
        this.dbUrl = dbUrl;
    }

    public String getDbName() {
        return dbName;
    }

    public void setDbName(String dbName) {
        this.dbName = dbName;
    }

    public String getDbUser() {
        return dbUser;
    }

    public void setDbUser(String dbUser) {
        this.dbUser = dbUser;
    }

    public String getDbPassword() {
        return dbPassword;
    }

    public void setDbPassword(String dbPassword) {
        this.dbPassword = dbPassword;
    }
}
```

To test our properties, let’s create a configuration class that implements <code>**CommandLineRunner**</code> so that we can log all the properties configured in its <code>**_run_**</code> method. In this class, we will have these three configuration classes autowired since they are spring managed beans.

```java
package com.habeebcycle.configurationpropertiesannotation;

import com.habeebcycle.configurationpropertiesannotation.config.DevConfigProps;
import com.habeebcycle.configurationpropertiesannotation.config.ProdConfigProps;
import com.habeebcycle.configurationpropertiesannotation.config.QaConfigProps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Configuration;

@Configuration
public class PropertiesConfig implements CommandLineRunner {

    private final DevConfigProps devConfigProps;
    private final QaConfigProps qaConfigProps;
    private final ProdConfigProps prodConfigProps;

    private static final Logger logger = LoggerFactory.getLogger(
          ConfigurationPropertiesAnnotationApplication.class);

    @Autowired
    public PropertiesConfig(DevConfigProps devConfigProps, 
          QaConfigProps qaConfigProps, ProdConfigProps prodConfigProps) {
        
        this.devConfigProps = devConfigProps;
        this.qaConfigProps = qaConfigProps;
        this.prodConfigProps = prodConfigProps;
    }


    @Override
    public void run(String... args) throws Exception {
        logger.info("-------------Dev Properties-----------------");
        logger.info("Name: {}", devConfigProps.getName());
        logger.info("Port: {}", devConfigProps.getPort());
        logger.info("DB Type: {}", devConfigProps.getDbType());
        logger.info("Version: {}", devConfigProps.getVersion());
        logger.info("DB Url: {}", devConfigProps.getDbUrl());
        logger.info("DB name: {}", devConfigProps.getDbName());
        logger.info("DB User: {}", devConfigProps.getDbUser());
        logger.info("DB Password: {}", devConfigProps.getDbPassword());

        logger.info("-------------QA Properties-----------------");
        logger.info("Name: {}", qaConfigProps.getName());
        logger.info("Port: {}", qaConfigProps.getPort());
        logger.info("DB Type: {}", qaConfigProps.getDbType());
        logger.info("Version: {}", qaConfigProps.getVersion());
        logger.info("DB Url: {}", qaConfigProps.getDbUrl());
        logger.info("DB name: {}", qaConfigProps.getDbName());
        logger.info("DB User: {}", qaConfigProps.getDbUser());
        logger.info("DB Password: {}", qaConfigProps.getDbPassword());

        logger.info("-------------Prod Properties---------------");
        logger.info("Name: {}", prodConfigProps.getName());
        logger.info("Port: {}", prodConfigProps.getPort());
        logger.info("DB Type: {}", prodConfigProps.getDbType());
        logger.info("Version: {}", prodConfigProps.getVersion());
        logger.info("DB Url: {}", prodConfigProps.getDbUrl());
        logger.info("DB name: {}", prodConfigProps.getDbName());
        logger.info("DB User: {}", prodConfigProps.getDbUser());
        logger.info("DB Password: {}", prodConfigProps.getDbPassword());
    }
}
```

Running the application as a SpringBoot application will log the following into the console:

```txt
---------------------Dev Properties-------------------------
2020-03-01 17:26:25.625 [main]: Name: Development Application
2020-03-01 17:26:25.626 [main]: Port: 8091
2020-03-01 17:26:25.626 [main]: DB Type: Maria DB
2020-03-01 17:26:25.626 [main]: Version: 1.0.alpha
2020-03-01 17:26:25.626 [main]: DB Url: jdbc:mariadb://localhost:3306/
2020-03-01 17:26:25.626 [main]: DB name: studentDB
2020-03-01 17:26:25.626 [main]: DB User: root
2020-03-01 17:26:25.627 [main]: DB Password: root
---------------------QA Properties-------------------------
2020-03-01 17:26:25.627 [main]: Name: QA Test Application
2020-03-01 17:26:25.627 [main]: Port: 8092
2020-03-01 17:26:25.627 [main]: DB Type: Mongo DB
2020-03-01 17:26:25.627 [main]: Version: 1.2.beta
2020-03-01 17:26:25.627 [main]: DB Url: mongodb://mongodb0.example.com:27017/
2020-03-01 17:26:25.627 [main]: DB name: studentDB
2020-03-01 17:26:25.627 [main]: DB User: admin
2020-03-01 17:26:25.627 [main]: DB Password: admin
---------------------Prod Properties-------------------------
2020-03-01 17:26:25.627 [main]: Name: Production Application
2020-03-01 17:26:25.627 [main]: Port: 8091
2020-03-01 17:26:25.627 [main]: DB Type: MySQL
2020-03-01 17:26:25.628 [main]: Version: 1.0.1
2020-03-01 17:26:25.628 [main]: DB Url: jdbc:mysql://localhost:3308/
2020-03-01 17:26:25.628 [main]: DB name: studentDB
2020-03-01 17:26:25.628 [main]: DB User: admin-prod
2020-03-01 17:26:25.628 [main]: DB Password: admin-prod
```



###Using <code>@ConfigurationProperties</code> to inject List properties.

We can also use <code>**@ConfigurationProperties**</code> to read List values from a properties file.

```properties
#application.properties

dev.mylist=SUN,MON,TUE,WED,THU,FRI,SAT
```

and use <code>**@ConfigurationProperties**</code> to inject the list as follows

```java
package com.habeebcycle.configurationpropertiesannotation.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
@ConfigurationProperties("dev")
public class DevConfigProps {
   private List<String> myList;

   public List<String> getMyList() {
      return myList;
   }

   public void setMyList(List<String> myList) {
      this.myList = myList;
   }

}
```
Logging the property gives

```txt
[SUN, MON, TUE, WED, THU, FRI, SAT]
```

###Using <code>@ConfigurationProperties</code> to inject Map (key-value pairs) properties

key-value pair properties can be read or injected with <code>**@ConfigurationProperties**</code> annotation as follows:

```properties
#application.properties

dev.db.url=jdbc:mysql://localhost:3308/
dev.db.name=student_marks
dev.db.user=root
dev.db.password=root
```

From the properties file above, we can see that after the prefix <code>**dev**</code>, we have <code>**db**</code>. This <code>**db**</code> will be the variable to use to inject the values as a key-value map.

```java
package com.habeebcycle.configurationpropertiesannotation.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

@Configuration
@ConfigurationProperties("dev")
public class DevConfigProps {
private Map<String, String> db;

   public Map<String, String> getDb() {
      return db;
   }

   public void setDb(Map<String, String> db) {
      this.db = db;
   }

}
```

Spring will automatically inject the nested properties into the <code>**db**</code> variables as Map variable.

Logging out the property value of <code>**devConfigProps.getDb()**</code>, will output the following:

```json
{
   "url": "jdbc:mysql://localhost:3308/",
   "name": "student_marks",
   "user": "root",
   "password": "root"
}
```

###Using <code>@ConfigurationProperties</code> to inject a class (Object) from a properties file

Let’s create a class called <code>**Student.java**</code>

```java
package com.habeebcycle.configurationpropertiesannotation.model;

public class Student {

    private String id;
    private String name;
    private int age;
    private String level;
    private double mark;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getLevel() {
        return level;
    }

    public void setLevel(String level) {
        this.level = level;
    }

    public double getMark() {
        return mark;
    }

    public void setMark(double mark) {
        this.mark = mark;
    }
}
```

If we have a properties file as follows

```properties
#application.properties

dev.student.id=ABC123XYZ
dev.student.name=Habeeb Okunade
dev.student.age=34
dev.student.level=700J
dev.student.mark=99.4
```

From the properties file above, we can see that after the prefix <code>**dev, we have <code>**student**</code>. This <code>**student**</code> will be the variable to use to inject the values as a <code>**Student**</code> object.

```java
package com.habeebcycle.configurationpropertiesannotation.config;

import com.habeebcycle.configurationpropertiesannotation.model.Student;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;


@Configuration
@ConfigurationProperties("dev")
public class DevConfigProps {

    private Student student;

    public Student getStudent() {
        return student;
    }

    public void setStudent(Student student) {
        this.student = student;
    }

}
```

Spring will automatically inject the nested properties into the <code>**student**</code> object as Student variable.

Logging out the property value of <code>**devConfigProps.getStudent()**</code>, will output the following:

```json
{
   "id": "ABC123XYZ",
   "name": "Habeeb Okunade",
   "age": 34,
   "level": "700J",
   "mark": 99.4
}
```

###Using <code>@ConfigurationProperties</code> on a <code>@Bean</code> method

We can also use <code>**@ConfigurationProperties**</code> annotation on <code>**@Bean**</code>-annotated methods.

This approach may be particularly useful when we want to bind properties to a third-party component that’s outside of our control.

Let’s create a simple <code>**School**</code> class to show how to use this:

```java
package com.habeebcycle.configurationpropertiesannotation.model;

public class School{

    private String id;
    private String name;
    private int size;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

}
```

Now, let’s see how we can use <code>**@ConfigurationProperties**</code> on a <code>**@Bean**</code> method to bind externalized properties to the <code>**School**</code> instance:

```java
package com.habeebcycle.configurationpropertiesannotation.config;

@Configuration
public class ExternalConfigProperties {
 
    @Bean
    @ConfigurationProperties("school")
    public School school() {
        return new School();
    }
}
```

By this, any <code>school</code>-prefixed property will be mapped to the <code>**School</code> instance managed by the Spring context.



###Using <code>**@ConfigurationProperties**</code> for property validation

<code>**@ConfigurationProperties**</code> provides validation of properties using the JSR-303 format. This allows our properties to come out neatly. Consider the following class:


```java
package com.habeebcycle.configurationpropertiesannotation.config;

import org.hibernate.validator.constraints.Length;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

@Configuration
@ConfigurationProperties("validate")  
@Validated
public class ConfigValidation {

    @NotNull
    private String dbUrl;

    @Length(min = 4, max = 10)
    private String dbUser;

    @Min(8080)
    @Max(8100)
    private int dbPort;

    @Pattern(regexp = "^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,6}$")
    private String adminEmail;

    public String getDbUrl() {
        return dbUrl;
    }

    public void setDbUrl(String dbUrl) {
        this.dbUrl = dbUrl;
    }

    public String getDbUser() {
        return dbUser;
    }

    public void setDbUser(String dbUser) {
        this.dbUser = dbUser;
    }

    public int getDbPort() {
        return dbPort;
    }

    public void setDbPort(int dbPort) {
        this.dbPort = dbPort;
    }

    public String getAdminEmail() {
        return adminEmail;
    }

    public void setAdminEmail(String adminEmail) {
        this.adminEmail = adminEmail;
    }
}
```

This class has some validation rules for the <code>**@ConfigurationProperties**</code> annotation to work.

The <code>**@NotNull**</code> annotation tells the Spring that the <code>**dbUrl**</code> field is mandatory and throws an error if not found.

The <code>**@Length(min=4, max=6)**</code> annotation validate <code>**dbUser**</code> to only inject property of a minimum of 4 characters and 6 characters long.

The <code>**@Min**</code> and <code>**@Max**</code> annotation to inject <code>**port**</code> value in the range of 8080 to 8100.

lastly, the <code>**adminEmail**</code> property must match the pattern provided as <code>**@Pattern(regexp = "^[a-z0–9._%+-]+@[a-z0–9.-]+\\.[a-z]{2,6}$")**</code>

This helps us reduce a lot of if-else conditions in our code and makes it look much cleaner and concise. 

If any of these validations fail, then the main application would fail to start with an <code>**IllegalStateException**</code>.

In this write-up, I have shown how to use <code>**@ConfigurationProperties**</code> to read /inject configuration properties from a properties file and explained some of the handy features it provides like property mapping, binding and validation.