Spring Boot makes is very easy to read values from an external properties file. Without having to do any configuration, you are able to inject values to instance fields using the @Value
annotation or using Spring’s Environment
abstraction. But both of these solutions require you to specify the property value as a string. Spring Boot also provides a type-safe way to handle configuration by allowing you to create a bean and populating it with property values from your configuration file.
Introducing @ConfigurationProperties
Let’s have a look at the following example application.yml
file (this will work with a .properties
file as well).
application:
emailServer: 192.168.1.2
serviceUser: user
servicePassword: password
This configuration file has three properties nested under the application
namespace. The following is a Java class declaring these properties as instance variables.
@ConfigurationProperties("application")
public class ApplicationProperties {
private String emailServer;
private String serviceUser;
private String servicePassword;
//getters and setters
}
Observant readers noticed that this class is annotated with @ConfigurationProperties("application")
. This instructs Spring Boot to bind property values from your configuration file to instance fields. "application"
specifies that only properties with the given prefix should be bound to this object.
Before you can start injecting @ConfigurationProperties
beans you need to enable configuration properties by adding @EnableConfigurationProperties(ApplicationProperties.class)
to a configuration bean.
@SpringBootApplication
@EnableConfigurationProperties(ApplicationProperties.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Now that this is done, feel free to inject ApplicationProperties
as you would inject any other bean.
@RestController
public class TestResource {
private final ApplicationProperties applicationProperties;
@Autowired
public TestResource(ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
}
@GetMapping
public String get() {
return applicationProperties.getServicePassword();
}
}
Configuration validation
Having configuration properties defined in a bean enables you to validate them using Java Bean Validation. Let’s say the servicePassword
property must always be defined. Otherwise your application’s state is invalid. That can be easily achieved by annotating the field with @NotNull
.
@NotNull
private String servicePassword;
If the application is started with an applicaiton.yml
that does not include servicePassword
, Spring Boot throws an exception on startup and the application fails to start.
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target io.indrek.ApplicationProperties@58a55449 failed:
Property: application.servicePassword
Value: null
Reason: may not be null
Action:
Update your application's configuration
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
Nested configuration
What if I add another level of nesting into my applicaiton.yml
file? Can I still use my ApplicationProperties
as a single source of configuration values? The answer to this question is yes. But you need to do some modifications first. In the following example, the application has been configured with a remoteHost
.
application:
emailServer: 192.168.1.2
serviceUser: user
servicePassword: password
remoteHost:
hostname: 192.168.1.10
To access that from Java, you need to create a new class for each level of nesting. In this case, you would need to create a new static inner class for the remoteHost
field and include all of it’s sub-fields as instance variables. The following is the updated ApplicationProperties
class.
@ConfigurationProperties("application")
public class ApplicationProperties {
private String emailServer;
private String serviceUser;
@NotNull
private String servicePassword;
private RemoteHost remoteHost;
//getters and setters
public static class RemoteHost {
private String hostName;
//getters and setters
}
}
Spring Boot will take care of instantiating the inner class. In your application code, you can access the newly added property by calling applicationProperties.getRemoteHost().getHostName()
.
Easy refactoring
Using @ConfigurationProperties
allows you to manage configuration values in a type-safe manner. Refactoring is made easier for you since you have a single class where all property values are defined. Consider the @Value
annotation for a moment. Injecting a property using @Value
requires you to specify the property as a string. What if you have multiple places in your codebase where this property is used? If your IDE is not smart enough, you would need to do a project wide search and replace when renaming a single property in the properties file.
Farewell
I hope you gained some new ideas reading this. A class holding all configuration values has its merits, namely, type-safety, advantages in refactoring and validation can be in one place. But don’t go and start replacing all of your @Value
annotations with configuration classes just yet. Use common sense. Sometimes it is less work to just use @Value
or Spring’s Environment
abstraction.