Introduction
In this article, we'll be diving into Configuring Spring Boot Properties.
Spring allows developers to configure a vast amount of properties for their projects. Spring Boot, besides allowing developers to start off with a project from scratch a lot more easily and time friendly than Spring, also makes it a lot easier to configure properties for your applications.
There are multiple ways to configure a Spring project:
- Java-based
- XML-based
- Properties-based
Java and XML-based property configuration was a classic way of configuring Spring applications before Spring Boot introduced us with an application.properties
file.
This addition allows us to externally configure the application and easily access properties defined in the file.
By default, the application.properties
file can be used to store property pairs, though you can also define any number of additional property files.
To register a property file, you can annotate a @Configuration
class with the additional @PropertySource
annotation:
@Configuration
@PropertySource("classpath:custom.properties")
public class ConfigClass {
// Configuration
}
Using this method, you can register any amount of additional .properties
files:
@Configuration
@PropertySource("classpath:custom.properties")
@PropertySource("classpath:another.properties")
public class ConfigClass {
// Configuration
}
Injecting Spring Boot Properties
Application Setup
The easiest way to start with a skeleton project is to use Spring Initializr. Select your preferred version of Spring Boot, add the Web
dependency and generate it as a Maven project:
If you open the project, you will notice that a file application.properties
is being kept at the src/main/resources
path.
This is the default file Spring relies upon to load the properties. We can write our custom or Spring-specific properties as key-value pairs here:
message.default.welcome=Welcome...
message.default.goodbye=Goodbye...
Instead of the properties
file, we can also use a .yml
file and define the same properties as:
message:
default:
welcome: Welcome...
goodbye: Goodbye...
This works because of the SnakeYaml jar present in the classpath. YAML files are more concise and support maps, lists, etc.
It's up to you and your team which type to use. We will be using the .properties
type in this tutorial.
Injecting Properties Using @Value
Let's see how we can use these properties in a simple REST API:
@RestController
public class GreetController {
@Value("${message.default.welcome}")
private String welcomeMessage;
@Value("${message.default.goodbye}")
private String goodBye;
@RequestMapping("/welcome")
public String welcome() {
return welcomeMessage;
}
@RequestMapping("/bye")
public String bye() {
return goodBye;
}
}
This is pretty straightforward. Using the @Value
annotation, we can inject the values from the application.properties
file into class fields in the Spring-managed bean GreetController
.
Then we have a couple of REST endpoints that simply return these values:
Using @Value
allows you to set a default value if the requested one, for any reason, isn't available:
@Value("${message.default.welcome:SomeDefaultValue}")
private String welcomeMessage;
If the message.default.welcome
value isn't present, the value will be set as SomeDefaultValue
.
If you'd like to read more about the @Value
annotation, we've got an in-depth article on that!
Injecting Properties Using @ConfigurationProperties
If our properties have some common context like the same prefix, we can use the @ConfigurationProperties
annotation which will map these properties to Java objects:
@Configuration
@ConfigurationProperties(prefix = "message.default")
public class MessageProperties {
private String welcome;
private String goodbye;
// Getters and Setters
}
@Configuration
will tell Spring to create a bean of this class.@ConfigurationProperties
will initialize the fields with corresponding property names.
We can now use this bean in other Spring-managed beans:
@Autowired
MessageProperties messageProperties;
Overriding Properties
Naturally, as our application environment expands and changes (development, QA, production, etc.), some of our properties will change as well. These can interfere with each others if we don't segregate them in some way.
We achieve this by maintaining different files or getting the values of the properties through environment variables.
Using Spring Profiles
The most common way to write "changing" properties is to store them in different files. These files are environment-specific and our application can load them based on the environment variables.
Spring Boot provides a very elegant way to handle this.
All we have to do is follow a naming convention - application-<environment>.properties
for our property files:
- application-dev.properties
- application-qa.properties
- application-production.properties, etc
To notify Spring which files to use, we have to set an environment variable - spring.profiles.active
.
That being said, if the value of spring.profiles.active
is dev
, for example, Spring boot will load the application-dev.properties
file and likewise.
Note: application.properties
is always loaded, irrespective of the spring.profiles.active
value. If there is the same key-value present both in application.properties
and application-<environment>.properties
, the latter will override the former.
Typically we write all the common properties of every environment in application.properties
and override environment-specific properties using the profile-specific application-<environment>.properties
.
Let's see this by creating a application-dev.properties
:
message.default.welcome = Welcome to DEV environment...
There are few ways to set up spring.profiles.active
variable.
If we are running the application through Eclipse, we can set this in VM arguments:
We can set it in the OS environment variables, like in Windows:
Let's start our application and in the logs, you can see the dev
profile being loaded:
Lets's check both of our previous REST endpoints:
As we can see, message.default.welcome
value came from application-dev.properties
file and message.default.goodbye
property came from application.properties
.
We can have multiple values in spring.profiles.active
like dev,qa
:
Any duplicate key would be overridden by the last profile, in the above case being qa
.
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
We can also pass in the spring.profiles.active
as a command-line argument like:
java -jar -Dspring.profiles.active=dev greeting-service-0.0.1-SNAPSHOT.jar
Creating application.properties from Build Location
We can also override the internal properties by creating an application.properties
file at the same level from where the .jar
is executed. Spring context will override properties using this newly created file.
This is a great way of distributing your application to others, who can override certain properties based on their environment, like database configurations for example.
There are other ways to externalize your properties too, such as OS environment variables, command-line arguments, etc. The order in which Spring considers it can be found here.
Externalizing Properties Using Cloud Configuration Server
Many of the applications built nowadays rely on the microservice architecture. Not only are these applications deployed separately but they could have multiple instances of themselves (based on load) and the total count could easily go above 100.
Managing properties in this particular architectural style via conventional methods requires too much effort. Also, to change a property, we have to re-build the application again and deploy it or at best have to restart the application. This requires downtime, which sort of defeats the whole purpose of microservices.
Another problem with the traditional approach, especially if the properties were externalized via file or environment variables is that there's no traceability. The latest ones are always taken and we don't know what the properties were before or who changed it.
Spring Cloud Config provides a centralized, externalized, secure and easy way for storing and serving configurations for applications for different environments:
In short, we have a Config Server running as a separate application that hooks to a Git repository.
When we start up a new application (Config Client), it gets all the needed properties from the Config Server. It doesn't matter if the application existed when we set the server up or not.
Creating a Config Server
As always, we start by using Spring Initializr.
Select your preferred version of Spring Boot, add the Config Server dependency and generate it as a Maven project:
By annotating our main class with @EnableConfigServer
, we mark it as being a config server:
@SpringBootApplication
@EnableConfigServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
And now, we have to set up a few things in the application.properties
file:
server.port = 8888
spring.cloud.config.server.git.uri = https://github.com/dhananjay12/spring-cloud-config
spring.cloud.config.server.git.searchPaths = app-properties
Here, we defined the port on which config server would be running. Then we specified the Git URL that it needs to hook for properties.
Note: By default, Spring searches for the property files at the root. If we have to specify a particular folder we can provide the location via searchPaths
.
Here's how the Git repo looks like:
We can start the config server now. If you'd like to check the configuration of the Spring Config Server, following the convention - http://localhost:8888/<application-name>/<spring-profiles>
will show us all the needed information.
In our case, it would be - http://localhost:8888/greeting-service-cloud/default:
Creating a Config Client
Let's create the same greeting service but with a couple of extra dependencies:
Here, we created greeting-service-cloud
service with Web
, Config Client
, and Actuator
dependencies.
It has the same REST mappings as before, with the addition of @RefreshScope
annotation. This annotation allows the bean to be refreshed dynamically at runtime:
@RestController
@RefreshScope
public class GreetController {
@Value("${message.default.welcome}")
private String welcomeMessage;
@Value("${message.default.goodbye}")
private String goodBye;
@RequestMapping("/welcome")
public String welcome() {
return welcomeMessage;
}
@RequestMapping("/bye")
public String bye() {
return goodBye;
}
}
Apart from application.properties
we now have to create bootstrap.properties
, which is loaded before the application.properties
.
It is typically used by the Spring Config Client to get properties from the Spring Config Server:
spring.application.name = greeting-service-cloud
spring.cloud.config.uri = http://localhost:8888
Here, we first set the application name. The Spring Config Server will search for this file name in the Git repository and serve its contents.
We also have to mention where the Config Server is running by specifying it in spring.cloud.config.uri
.
Let's start this service and take a look at the logs:
Notice that it first got the properties from the Spring Config Server.
Note: If the Config Server is not reachable, the application won't start.
Now let's check our REST endpoints:
So we externalized our properties and have nice traceability of it in our Git repository. Some important points worth noting:
- We can use
spring-profiles-active
here too. If this variable is set in the Config Client environment for eg.dev
, it will be passed to the Config Server while requesting properties. The config server will then look forgreeting-service-cloud-dev.properties
in the Git repository and serve it to the client. - If there is a
application.properties
present in the Git repository, it will be served to all the clients in addition to other files. - If the Config Client requests properties, for example, say
dev
profile, the Config Server will returnapplication.properties
,application-dev.properties
andgreeting-service-cloud-dev.properties
. The common properties will be overridden by the last one.
Refreshing Properties without Restart
By default, the configuration values from properties files are ready or fetched at the application startup and not again. If there are some changes to be made, we still have to restart the application.
To solve this we added the Actuator dependency to our application. It provides some production-ready endpoints that can give insights regarding our application which can be used for administrative purposes.
We have to enable these endpoints manually by specifying management.endpoints.web.exposure.include = *
in the application properties.
Let's add this to the Git repo and restart the application. We can check many details of our application by visiting endpoints like http://localhost:8080/actuator/env, http://localhost:8080/actuator/mappings, etc.
The one we are interested in is the /actuator/refresh
. We can force a bean to refresh its configuration (i.e, to pull configuration again from the config server) by annotating the bean with @RefreshScope
.
Note: If a bean is refreshed then the next time the bean is accessed (i.e. a method is executed) a new instance is created.
This can be triggered by sending an empty HTTP POST request to the client’s refresh endpoint - http://<host:port>/actuator/refresh
.
Let's change the value of one in the Git repository to something else:
message.default.welcome = Welcome from cloud config server changed...
message.default.goodbye = Goodbye...
management.endpoints.web.exposure.include = *
Now lets trigger the refresh endpoint:
curl localhost:8080/actuator/refresh -d {} -H "Content-Type: application/json"
Check the /welcome
endpoint:
So, we were able to refresh the property of a running application without restarting it.
Conclusion
In this article, we've covered how to configure properties in our Spring Boot application.
Firstly, we've discussed simple ways to inject properties to our application and then changing/overriding these properties based on different environments.
Secondly, we've covered how to get properties from Spring Config Server and how to update properties without a rebuild or restart.
As always, the code for the examples used in this article can be found on Github.