How to bind property from external source in spring-boot application ?

As per Spring's documentation, Spring Boot allows us to externalize configurations, so you can work with the same application code in different environments. You can use property files, YAML files, environment variables, and command-line arguments to externalize configurations. But in this article we will mostly check how to read configurations from property or yml files. True externalization requires reading property or YAML files from external cloud sources like Consul, where Consul properties like consul host, port, and keys are provided to the application via environment variables. Then our same application code can run in different environments. We will cover that in some other article.

Here, we will discuss how configuration keys are bound to actual objects in Spring Boot applications. The most basic way to bind your configurations is from property or YAML files to POJO classes, which we can use later in the lifecycle of our application.

Spring Boot supports different kinds of binding:

  • Simple property binding
  • Collection-based binding
  • Array-based binding
  • Map-based binding
  • Nested properties

Let us check out these types of binding with an easy sample Spring Boot application. We are going to create a simple Spring Boot application that reads a configuration from a property file. The following is the structure for this simple project:

Project Structure

Let us have a look at our pom.xml first. Here we are using Spring Boot parent version 1.5.9.RELEASE.

<project xmlns="http://maven.apache.org/POM/4.0.0" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
        http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.tuturself</groupId>
	<artifactId>spring-boot-config</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>spring-boot-config</name>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.9.RELEASE</version>
	</parent>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

	</dependencies>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

And the following are the properties that we will bind to our POJO classes from the application.properties file:

# Cassandra Configuration
#############################################
# Collection based binding
cassandra.server=127.0.0.1:9042,127.0.0.2:9042
# Variable based binding
cassandra.user=dbUser1
cassandra.password=dbUsEr)!
# Nested class based binding
cassandra.keyspace.name=test_keyspace
cassandra.keyspace.readConsistency=ONE
cassandra.keyspace.writeConsistency=ONE
# Kafka Configuration
#############################################
# Collection based binding
kafka.server=127.0.0.1:5046,127.0.0.2:5046
# Nested class based binding
kafka.serializer.key=org.apache.kafka.common.serialization.StringSerializer
kafka.serializer.value=org.apache.kafka.common.serialization.StringSerializer
# Map based binding
kafka.topicMap.one=topic-1
kafka.topicMap[two]=topic-2

This property file has connection information and other configurations for Cassandra and Kafka. First, let's check the POJO classes, which we have used to bind these properties. Cassandra information will be bound to the CassandraConfiguration.java class:

package com.tuturself;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import lombok.AccessLevel;
import lombok.Data;
import lombok.Getter;
@Data
@ConfigurationProperties(prefix = "cassandra")
public class CassandraConfiguration {
    private List server;
    private String user;
    private String password;
    @Getter(AccessLevel.NONE)
    private Keyspace keyspace;
    public Keyspace getKeyspace() {
        if (this.keyspace == null) {
            this.keyspace = new Keyspace();
        }
        return this.keyspace;
    }
    @Data
    public class Keyspace {
        private String name;
        private String readConsistency;
        private String writeConsistency;
    }
}

The @ConfigurationProperties(prefix = "cassandra") annotation tells Spring to bind all properties with the prefix Cassandra to this class. Now let's look at the other binding strategies.

Variable-Based Binding

The following properties are directly bound to the matching attributes in the class:

cassandra.user=dbUser1 -->  private String user;
cassandra.password=dbUsEr)!  --> private String password;

Collection-Based Binding

The comma separated Cassandra hosts are bound to a List<String> in the Class. This is an example of collection-based binding:

cassandra.server=127.0.0.1:9042,127.0.0.2:9042 to private List<String> server.

Nested Property-Based Binding

The keyspace attributes are bound to an inner class named Keyspace. This is an example of nested property binding. For nested property binding, we need to provide a getter to create the Object, or we can create the Object while declaring it. We have provided a getter for it:

public Keyspace getKeyspace() {
    if (this.keyspace == null) {
        this.keyspace = new Keyspace();
    }
    return this.keyspace;
}

We can also create the class while declaring it in the following way:

private Keyspace keyspace = new Keyspace();

In that case, we do not need the following part in our getter method:

if (this.keyspace == null) {
    this.keyspace = new Keyspace();
}

The @Data annotation and @Getter(AccessLevel.NONE) are not related to Spring Boot property mapping. It is from Project Lombok, which will create the getters and setters in the domain class automatically. Read about project Lombok and its usages here.

Now let's check another domain class that contains a Kafka configuration named KafkaConfiguration.class:

package com.tuturself;
import java.util.List;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import lombok.Data;
@Data
@ConfigurationProperties(prefix = "kafka")
public class KafkaConfiguration {
    private List < String > server;
    private Map < String, String > topicMap;
    private Serializer serializer = new Serializer();
    @Data
    public class Serializer {
        private String key;
        private String value;
    }
}

Here we have used a new binding mechanism: map-based binding. Check how the topic-related information from Kafka Configuration section is bound to private Map<String,String> topicMap.

# Map based binding
kafka.topicMap.one=topic-1
kafka.topicMap[two]=topic-2
To
private Map<String,String> topicMap;

Now let's define the Main class for our Spring Boot application to test it:

package com.tuturself;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties({
    CassandraConfiguration.class,
    KafkaConfiguration.class
})
public class Application {
    @Autowired
    private CassandraConfiguration cassandraConfigurationInstance;
    @Autowired
    private KafkaConfiguration kafkaConfigurationInstance;
    private static CassandraConfiguration cassandraConfiguration;
    private static KafkaConfiguration KafkaConfiguration;
    @PostConstruct
    public void init() {
        cassandraConfiguration = cassandraConfigurationInstance;
        KafkaConfiguration = kafkaConfigurationInstance;
    }
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
        System.out.println(cassandraConfiguration.toString());
        System.out.println(KafkaConfiguration.toString());
    }
}

@EnableConfigurationProperties automatically maps POJOs to a set of properties in the Spring Boot configuration file (by default: application.properties).We get the following output when we run the Spring Boot application:

CassandraConfiguration(
    server=[127.0.0.1:9042, 127.0.0.2:9042], 
    user=dbUser1, password=dbUsEr)!, 
    keyspace=CassandraConfiguration.Keyspace(
        name=test_keyspace, 
        readConsistency=ONE, 
        writeConsistency=ONE
    )
)
KafkaConfiguration(
    server=[127.0.0.1:5046, 127.0.0.2:5046], 
    topicMap={two=topic-2, one=topic-1}, 
    serializer=KafkaConfiguration.Serializer(
        key=org.apache.kafka.common.serialization.StringSerializer, 
        value=org.apache.kafka.common.serialization.StringSerializer
    )
)

 

core java 12 spring 12 Spring Boot 12

FOLLOW US ON LinkedIn



Explore Tutu'rself