Injecting YAML properties to java config class - Spring boot

In this post, we will see how to read values from the YAML file and inject them into the java model class.

YAML is a human-readable language used for configurations also the yaml solves hierarchical configuration where we can define complex structures.

If you don't know how to create a spring boot project, see the Create Spring Boot Application.


Spring boot Configuration Properties

@ConfigurationProperties is used when we want to inject a group of properties with the same prefix into a Java POJO.

In the below the yaml, the employee is the prefix that contains firstname, lastname, age.

---
employee:
   firstname: bharath
   lastname: kumar
   age: 25
  

Spring boot injects value into fields using setter/getter methods. In the below class we have used the Lombok @Data annotation which generates setter/getter for us.


import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import lombok.Data;

@Data
@Component
@ConfigurationProperties(prefix = "employee")
@EnableConfigurationProperties
public class Employee {

	private String firstname;
	private String lastname;
	private int age;

}


Loading external custom YAML file

To load our custom yaml file we have to add a few configurations so that spring boot will load all external yaml files along with the application.yaml 

By default, @PropertySource doesn't read the YAML file. To load the yaml file we have to make use of yamlPropertiesfactorybean which will load yaml as properties.

In the below configuration class we have created a bean of yamlPropertiesfactorybean.

import java.io.IOException;
import java.util.Arrays;

import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

@Configuration
public class YAMLConfig {

	@Bean
	public static PropertySourcesPlaceholderConfigurer yamlproperties() {
		PropertySourcesPlaceholderConfigurer placeholderConfigurer = new PropertySourcesPlaceholderConfigurer();

		YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();

		ResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
		Resource[] yamlfiles = null;

		try {
			yamlfiles = patternResolver.getResources("classpath*:/*.yaml");
			System.out.println(Arrays.toString(yamlfiles));
		} catch (IOException e) {
			e.printStackTrace();
		}

		factoryBean.setResources(yamlfiles);
		placeholderConfigurer.setProperties(factoryBean.getObject());
		return placeholderConfigurer;
	}
}

Inject map from YAML

We can also define a map in YAML. In the below YAML file the phonenumbers is a map having a string as key and value pair.


---
employee:
   firstname: bharath
   lastname: kumar
   age: 25
   phonenumbers:
     office: 0442345555
     home: 911233333333
     personal: 9840002222
    

Inject list from YAML

In the yaml below, the employee skills is a list collection containing the skill set of employee e.g. java, AWS, etc which is of type string.


---
employee:
   firstname: bharath
   lastname: kumar
   age: 25
   phonenumbers:
     office: 0442345555
     home: 911233333333
     personal: 9840002222
   skils:
     - java
     - spring
     - hibernate
     - aws     
    

Inject nested properties from YAML

We can also define nested values in YAML. These nested properties can be a separate class or inner class. 


---
employee:
   firstname: bharath
   lastname: kumar
   age: 25
   address:
     street: dhanasekran
     city: vice city
     state: GTA
     postalcode: 600024
   phonenumbers:
     office: 0442345555
     home: 911233333333
     personal: 9840002222
   skils:
     - java
     - spring
     - hibernate
     - aws     
    

For example, in employee YAML, the address is the nested property that contains employee address details. Also, we have created an inner class inside the employee class to hold the nested values (address).

Now the updated class looks like below.

import java.util.List;
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import lombok.Data;

@Data
@Component
@ConfigurationProperties(prefix = "employee")
@EnableConfigurationProperties
public class Employee {

	private String firstname;
	private String lastname;
	private int age;
	private Address address;
	private Map<String, String> phonenumbers;
	private List<String> skils;

	@Data
	static class Address {
		private String street;
		private String city;
		private String state;
		private long postalcode;
	}
}

Testing 

let write a JUnit to test whether values are injected correctly to the fields.

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.example.demo.config.Employee;

@SpringBootTest
class EmployeeTest {

  @Autowired
  Employee employee;

  @Test
  void checkTheYamlInjection() {

    System.out.println(employee.toString());

    assertEquals("bharath", employee.getFirstname());
    assertEquals("kumar", employee.getLastname());
    assertNotNull(employee.getAddress());

  }
}
  
Spring boot read yaml to pojo class


Constructor binding 

If we don't want to bind values using setter/getter methods, Then we can use constructor binding by @ConstrutorBinding annotation.

Let's update the employee.class to inject value using spring boot ConstrutorBinding 

import java.util.List;
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;

import lombok.AllArgsConstructor;
import lombok.ToString;

@AllArgsConstructor
@ConstructorBinding
@ConfigurationProperties(prefix = "employee")
@ToString
public class Employee {

	private String firstname;
	private String lastname;
	private int age;
	private Address address;
	private Map<String, String> phonenumbers;
	private List<String> skils;

	@AllArgsConstructor
	@ToString
	@ConstructorBinding
	static class Address {
		private String street;
		private String city;
		private String state;
		private long postalcode;
	}
}
  
If we have more than one constructor in class then we can place the annotation on the constructor which we want to use for binding values.

we have to add @ConfigurationPropertiesScan annotation to our main spring boot application class to enable support for configuration properties and the bean gets created for the class annotated with @ConfigurationProperties.


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

@SpringBootApplication
@ConfigurationPropertiesScan
public class SpringBootTutorialApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringBootTutorialApplication.class, args);
	}
}


Validation

Let's add bean validation to our class to ensure that values are injected and values are validated purposely. It's always better to add validation to configuration properties, so we find the possible configuration error earlier.

Add @Validated to our employee.class



import java.util.List;
import java.util.Map;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.validation.annotation.Validated;

import lombok.AllArgsConstructor;
import lombok.ToString;

@AllArgsConstructor
@ConstructorBinding
@ConfigurationProperties(prefix = "employee")
@ToString
@Validated
public class Employee {

  @NotBlank(message = "Firstname cannot be blank")
  private String firstname;

  @NotBlank(message = "Lastname cannot be blank")
  private String lastname;

  private int age;

  @NotNull(message = "Address must be present")
  private Address address;

  private Map < String, String > phonenumbers;

  private List < String > skils;

  @AllArgsConstructor
  @ToString
  @ConstructorBinding
  static class Address {
    private String street;
    private String city;
    private String state;
    private long postalcode;
  }
  
}

If we remove employee firstname then we will get the below error on application startup.


spring boot bean validation for configuration properties


Post a Comment

Previous Post Next Post

Recent Posts

Facebook