Reading External Configuration Properties in Spring
2 CommentsLast Updated on February 3, 2021 by jt
Enterprise Applications developed using the Spring Framework use different types of configuration properties to configure the application at runtime. These configuration properties help in connecting to databases, messaging systems, perform logging, caching, and lots more.
It is common to store configuration properties in external .properties and .yml files. There are various ways of reading external configuration properties in Spring.
In this post, we will see how to read external properties using annotations, such as @PropertySource
, @Environment
, @Value
, and @ConfigurationProperties
.
Reading as Property Value
In this post, I will use a Spring Boot application that performs operations on a Blog
entity. Also, I will use Lombok to generate code for the Blog
entity.
The code of the Blog
entity is this.
Blog.java
package guru.springframework.domain; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import javax.persistence.Entity; import javax.persistence.Id; @Entity @Data @NoArgsConstructor @AllArgsConstructor @Builder public class Blog { @Id private String blogId; private String blogTitle; private String authorName; private String blogDescription; }
The application.yml
file containing configuration properties of the application is this.
server: port: 8088 guru: springframework: blog: id: 202 title: The Huffington Post author: Arianna description: The history of political blogging might usefully be divided into the periods pre- and post-Huffington. toptitles: The Huffington Post, Solo Traveller, Meghna's Diary topbloggers: Meghna N;Arianna;Paul Grater; topblogs: {"The Huffington Post":"Arianna","Meghnas Diary":"Meghna N","Solo Traveller":"Paul Grater"}
Next, I will define a configuration class to access the properties defined in the preceding code. I will also define a bean to create a new blog.
The code for ExternalPropertyValueDemo.java
is this.
ExternalPropertyValueDemo.java
package guru.springframework.demo; import guru.springframework.domain.Blog; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import java.util.List; @Data @Configuration @PropertySource("classpath:application.yml") public class ExternalPropertyValueDemo { @Value("${guru.springframework.blog.id}") private String blogId; @Value("${guru.springframework.blog.title}") private String blogTitle; @Value("${guru.springframework.blog.author}") private String authorName; @Value("${guru.springframework.blog.description}") private String description; @Value("${guru.springframework.toptitles}") private String[] topTitles; @Value("#{'${guru.springframework.topbloggers}'.split(';')}") private List<String> topBloggers; @Bean("simpleBlog") public Blog createBlog() { Blog blog = new Blog(blogId, blogTitle, authorName, description); System.out.println(blog); return blog; } }
The preceding code uses the @PropertySource
annotation to specify an application.yml
file to load from the classpath. The code then injects the values of the properties of application.yml
in the class fields using the @Value
annotation.
The code also creates a Blog
bean named simpleBlog
initialized with the properties read from the application.yml
file.
To test this class, I will write a JUnit 5 test.
The test class, ExternalPropertyValueDemoTest
is this.
ExternalPropertyValueDemoTest.java
package guru.springframework.demo; import guru.springframework.domain.Blog; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest class ExternalPropertyValueDemoTest { @Autowired @Qualifier("simpleBlog") private Blog simpleBlog; @Autowired ExternalPropertyValueDemo externalPropertyValueDemo; private String[] expectedTopTitles; private List<String> expectedTopBloggersList; private Map<String, String> expectedTopBlogs; @BeforeEach public void setUp() { expectedTopTitles = new String[]{"The Huffington Post", "Solo Traveller", "Meghna's Diary"}; expectedTopBloggersList = new ArrayList<String>() { { add("Meghna N"); add("Arianna"); add("Paul Grater"); } }; expectedTopBlogs = new HashMap<String, String>() { { put("The Huffington Post", "Arianna"); put("Meghna's Diary", "Meghna N"); put("Solo Traveller", "Paul Grater"); } }; } @AfterEach public void tearDown() { expectedTopTitles = null; expectedTopBloggersList = null; expectedTopBlogs = null; } @Test public void testExternalPropertyReadwithValue() { assertThat(simpleBlog.getBlogId()).isEqualTo("202"); assertThat(simpleBlog.getBlogTitle()).isEqualTo("The Huffington Post"); assertThat(simpleBlog.getAuthorName()).isEqualTo("Arianna"); } @Test public void testExternalPropertyReadForArray() { assertThat(externalPropertyValueDemo.getTopTitles()).containsExactly("The Huffington Post", "Solo Traveller", "Meghna's Diary"); } @Test public void testExternalPropertyReadForList() { assertThat(externalPropertyValueDemo.getTopBloggers()).containsExactly("Meghna N", "Arianna", "Paul Grater"); } }
Reading as Environment Properties
Spring comes with the Environment
interface that represents the environment in which the current application is running. We can read configuration properties using this Environment
. I will define a configuration class for that.
The code for ExternalPropertyEnvironmentDemo
class is this.
ExternalPropertyEnvironmentDemo.java
package guru.springframework.demo; import guru.springframework.domain.Blog; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; import java.util.Arrays; import java.util.List; @Data @NoArgsConstructor @Configuration @PropertySource(value = "classpath:application.yml", ignoreResourceNotFound = true) //Class to Demonstrate @Environment public class ExternalPropertyEnvironmentDemo { /** * Environment to get the property values */ private Environment environment; private String[] topTitles; private List<String> topBloggers; @Autowired public void ExternalPropertyEnvironmentDemo(Environment environment) { this.environment = environment; this.setTopTitles(); this.setTopBloggers(); } @Bean("environmentBlog") public Blog createBlog() { Blog blog = new Blog(environment.getProperty("id"), environment.getProperty("title"), environment.getProperty("author"), environment.getProperty("description")); return blog; } public void setTopTitles() { this.topTitles = environment.getProperty("toptitles", String[].class); } public void setTopBloggers() { this.topBloggers = Arrays.asList(environment.getProperty("topbloggers", "").split(";")); } }
In the preceding code, I have used ignoreResourceNotFound=true
attribute in @PropertySource
annotation to avoid java.io.FileNotFoundException
.
Next, the code autowires in the Environment
. Then, the code calls the environment.getProperty()
method to get the property value from the application.yml
file.
The code to test the above class is this.
ExternalPropertyEnvironmentDemoTest.java
package guru.springframework.demo; import guru.springframework.domain.Blog; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest class ExternalPropertyEnvironmentDemoTest { @Autowired @Qualifier("environmentBlog") private Blog environmentBlog; @Autowired private ExternalPropertyEnvironmentDemo externalPropertyEnvironmentDemo; private String[] expectedTopTitles; private List<String> expectedTopBloggersList; private Map<String, String> expectedTopBlogs; @BeforeEach public void setUp() { expectedTopTitles = new String[]{"The Huffington Post", "Solo Traveller", "Meghna's Diary"}; expectedTopBloggersList = new ArrayList<String>() { { add("Meghna N"); add("Arianna"); add("Paul Grater"); } }; expectedTopBlogs = new HashMap<String, String>() { { put("The Huffington Post", "Arianna"); put("Meghna's Diary", "Meghna N"); put("Solo Traveller", "Paul Grater"); } }; } @AfterEach public void tearDown() { expectedTopTitles = null; expectedTopBloggersList = null; expectedTopBlogs = null; } @Test public void testExternalPropertyReadwithValue() { assertThat(environmentBlog.getBlogId()).isEqualTo("202"); assertThat(environmentBlog.getBlogTitle()).isEqualTo("The Huffington Post"); assertThat(environmentBlog.getAuthorName()).isEqualTo("Arianna"); } @Test public void testExternalPropertyReadForArray() { assertThat(externalPropertyEnvironmentDemo.getTopTitles()).containsExactly("The Huffington Post", "Solo Traveller", "Meghna's Diary"); } @Test public void testExternalPropertyReadForList() { assertThat(externalPropertyEnvironmentDemo.getTopBloggers()).containsExactly("Meghna N", "Arianna", "Paul Grater"); } }
Multiple Properties File
Next, I will show you how to fetch properties from multiple property files.
For this, I will use @PropertySources
annotation that contains the classpath for multiple properties file.
I will create two properties file.
The code for demoa.properties
is this.
demoa.properties
guru.springframework.blog.id= 202 guru.springframework.blog.title= The Huffington Post guru.springframework.blog.author= Arianna guru.springframework.blog.description= The history of political blogging might usefully be divided into the periods pre- and post-Huffington.
The code for demob.properties
is this.
demob.properties
guru.springframework.microblog.blogger= JT guru.springframework.microblog.blogid= 12 guru.springframework.microblog.content= Spring Boot releases version 3
Next, I will add a configuration class, MultiplePropertySourceDemo
to access properties from both the demoa.properties
and demob.properties
files.
The code of the MultiplePropertySourceDemo
class is this.
MultiplePropertySourceDemo.java
package guru.springframework.demo; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.annotation.PropertySources; import org.springframework.core.env.Environment; @Data @Configuration @PropertySources({ @PropertySource("classpath:demoa.properties"), @PropertySource("classpath:demob.properties") }) public class MultiplePropertySourceDemo { //Fields to read from demoa.properties private String blogId; private String blogTitle; //Fields to read from demob.properties private String microBlogId; private String content; @Autowired public MultiplePropertySourceDemo(Environment environment) { this.blogId = environment.getProperty("guru.springframework.blog.id"); this.blogTitle = environment.getProperty("guru.springframework.blog.title"); this.microBlogId = environment.getProperty("guru.springframework.microblog.blogid"); this.content = environment.getProperty("guru.springframework.microblog.content"); } }
In the preceding code, I have accessed the values from demoa.properties
and demob.properties
using the environment.getProperty()
method.
The code to test the above class is this.
MultiplePropertySourceDemoTest.java
package guru.springframework.demo; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest class MultiplePropertySourceDemoTest { @Autowired MultiplePropertySourceDemo multiplePropertySourceDemo; @Test public void testForBlogId() { assertThat(multiplePropertySourceDemo.getBlogId()).isEqualTo("202"); } @Test public void testForBlogTitle() { assertThat(multiplePropertySourceDemo.getBlogTitle()).isEqualTo("The Huffington Post"); } @Test public void testForMicroBlogId() { assertThat(multiplePropertySourceDemo.getMicroBlogId()).isEqualTo("12"); } @Test public void testForMicroBlogContent() { assertThat(multiplePropertySourceDemo.getContent()).isEqualTo("Spring Boot releases version 3"); } }
Using @ConfigurationProperties
Next, we will see how to map the entire Properties or YAML files to an object. For this, I will use @ConfigurationProperties
annotation.
I will add a dependency to generate metadata for the classes annotated with @ConfigurationProperties
.
The dependency code in pom.xml
is this.
pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
I will access values from the demob.properties
file.
I will create a MicroBlog
domain class to map the properties to.
The code for MicroBlog
is this.
MicroBlog.java
package guru.springframework.domain; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; @Configuration("microBlog") @PropertySource("classpath:demob.properties") @Data @NoArgsConstructor @ConfigurationProperties(prefix="guru.springframework.microblog") public class MicroBlog { private String blogid; private String content; private String blogger; }
In the above code, I have defined the classpath as demob.properties
in the @PropertySource
annotation. Next, I have used the @ConfigurationProperties
annotation to set the prefix for the properties to fetch.
I will define the MicroBlog
class as a bean ins configuration class
The code of the configuration class, ConfigurationPropertiesDemo
class is this.
ConfigurationPropertiesDemo.java
package guru.springframework.demo; import guru.springframework.domain.MicroBlog; import org.springframework.context.annotation.Bean; public class ConfigurationPropertiesDemo { @Bean("microBlog") public MicroBlog createBlog() { return new MicroBlog(); } }
The code to test the above class is this.
package guru.springframework.demo; import guru.springframework.domain.MicroBlog; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest class ConfigurationPropertiesDemoTest { @Autowired MicroBlog microBlog; @Test public void testConfigurationProperty() { assertThat(microBlog.getBlogid()).isEqualTo("12"); assertThat(microBlog.getBlogger()).isEqualTo("JT"); assertThat(microBlog.getContent()).isEqualTo("Spring Boot releases version 3"); } }
Profile-based Configuration Settings
Let’s now see how to fetch properties based on profiles. For this, I will use spring.profiles.active
to set the active profile.
I will use two property files application-dev.yml
that contains embedded h2 console configuration properties and application-prod.yml
containing MySQL configuration properties.
I will add dependencies for MySQL, embedded H2, and Spring Data JPA.
The dependency code in pom.xml
is this.
pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> <version>1.4.199</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.13</version> </dependency>
I will add configuration properties in the YAML file.
The code for application-dev.yml
is this.
application-dev.yml
spring: h2: console: enabled: true path: /h2-console datasource: username: sa password: url: jdbc:h2:mem:testdb driverClassName: org.h2.Driver database-platform: org.hibernate.dialect.H2Dialect
The code for application-prod.yml
is this.
application-prod.yml
spring: jpa: hibernate: ddlAuto: update properties: hibernate: dialect: org.hibernate.dialect.MySQL5Dialect datasource: username: root password: root url: jdbc:mysql://localhost:3306/blogDb?useSSL=false&createDatabaseIfNotExist=true&allowPublicKeyRetrieval=true driverClassName: com.mysql.jdbc.Driver
Next, I will set the active profile in application.yml
.
spring: profiles: active: prod
In the preceding code, I have set prod
as the active profile. As a result, Spring will pick the properties present in the application-prod.yml
file at runtime.
Now I will create a class to load data when the application starts.
package guru.springframework.bootstrap; import guru.springframework.domain.Blog; import guru.springframework.repository.BlogRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; @Component public class BootstrapData implements ApplicationListener<ContextRefreshedEvent> { private BlogRepository blogRepository; @Autowired private BootstrapData(BlogRepository blogRepository) { this.blogRepository = blogRepository; } public BootstrapData() { } @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { Blog blog = Blog.builder() .blogId("201") .blogTitle("One Mile At A Time") .blogDescription("The latest travel news, reviews, and strategies to maximize elite travel status") .authorName("Mat Tree") .build(); try { blogRepository.save(blog); } catch (Exception exception) { exception.printStackTrace(); } } }
In the preceding code, the BootstrapData
class implements ApplicationListener
interface. In onApplicationEvent()
method, I haveWe will pre-filled the database whenever the application starts.
When I run the application, I get the following as output.
The following profiles are active: prod
Summary
I often see @Value
being used to read configuration properties. I’m not a particular fan of this approach – particularly in enterprise applications. This is because you end up with scattered configuration code (@Value) across your application classes.
Now, what happens if a property name changes?
You end up trying to find out all affected @Value
code and updating them with the new property name.
Instead, you should encapsulate configuration as a service of your application. If you do so, you will have a single point of responsibility to load and get your configuration from.
By using @ConfigurationProperties
you can easily encapsulate your configuration in a separate class.
DivyaGanesh
Thanks you share the best software technologies for freshers and experience candidates to upgrade the next level in an Software Industries Technologies.
Prasanna
In case of spring mvc application can we achieve hot reloading (without restarting server instance can we get updated properties).
if yes how can we achieve that?