Reading External Configuration Properties in Spring

Reading External Configuration Properties in Spring

0 Comments

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 MicroBlogclass 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.

About SFG Contributor

Staff writer account for Spring Framework Guru

    You May Also Like

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    This site uses Akismet to reduce spam. Learn how your comment data is processed.