Sometimes we need some structured, static data in our application. Perhaps the static data is a workaround until we have built the full feature that stores the data in the database and allows users to maintain the data themselves. Or we just need a way to easily maintain and access rarely changing data without the overhead of storing it in a database.
Use cases might be:
- maintaining a large enumeration containing structured information that changes every once in a while - we don’t want to use enums in code because we don’t want to recompile the whole application for each change, or
- displaying static data in an application, like the name and address of the CEO in the letterhead of an invoice or a “Quote of the Day” on a web page, or
- using any structured data you can think of that you don’t want to maintain in code nor in the database.
With its @ConfigurationProperties
feature, Spring Boot supports access to structured data from one or more configuration files.
In this article, we’ll have a look at:
- how to create a configuration file with the data,
- how to create an integration test that verifies the setup, and
- how to access the data in the application.
We’ll take the “Quote of the Day” use case as an example (I actually built that a couple weeks back as a farewell present to my previous team :)).
Example Code
This article is accompanied by a working code example on GitHub.Storing Static Data in a Config File
First, we create a YAML file quotes.yml
that contains our static data:
static:
quotes:
- text: "A clever person solves a problem. A wise person avoids it."
author: "Albert Einstein"
- text: "Adding manpower to a late software project makes it later."
author: "Fred Brooks"
If you prefer properties files over YAML, you can use that instead. It’s just easier to represent nested data structures with YAML.
In our case, each quote has a text and an author. Each quote will later be represented in a Quote
object.
Note that we prefixed the data with static:quotes
. This is necessary to create a unique namespace because Spring Boot will later merge the content of this config file with the rest of its configuration.
Making Spring Boot Aware of the Config File
Now we have to make Spring Boot aware of this configuration file. We can do this by setting the system property spring.config.location
each time we start the Spring Boot application:
-Dspring.config.location=./,./quotes.yml
This tells Spring Boot to search for an application.properties
or application.yml
file in the current folder (which is the default) and to additionally load the file quotes.yml
.
This is all we need to do for Spring Boot to load our YAML file and expose the content within our application.
Accessing the Static Data
Now to the code.
First off, we need a Quote
data structure that serves as a vessel for the configuration data:
@ConstructorBinding
class Quote {
private final String text;
private final String author;
Quote(String text, String author) {
this.text = text;
this.author = author;
}
// getters and setters omitted
}
The Quote
class only has simple String
properties. If we have more complex data types, we can make use of custom converters that convert the configuration parameters (which are always String
s) to the custom types we need.
Note that Quote
s are immutable, taking all their state in the constructor. Because of this, we need to add the @ConstructorBinding
annotation to the class, telling Spring Boot to use the constructor for instantiation. Otherwise, we’ll get a binding error (see box below).
Next, we take advantage of Spring Boot’s @ConfigurationProperties
feature to bind the static data to a QuotesProperties
object:
@Component
@ConfigurationProperties("static")
public class QuotesProperties {
private final List<Quote> quotes;
public QuotesProperties(List<Quote> quotes) {
this.quotes = quotes;
}
public List<Quote> getQuotes(){
return this.quotes;
}
}
This is where our namespace prefix comes into play. The QuotesProperties
class is bound to the namespace static
and the quotes
prefix in the config file binds to the field of the same name.
Getting a "Binding failed" error?
Spring Boot is a little intransparent in the error messages when the binding of a configuration property fails. You might get an error message like Binding to target ... failed ... property was left unbound
without knowing the root cause.
In my case, the root cause was always that I did not provide a default constructor and getters and setters in one of the classes that act as a data structure for the configuration properties (Quote
, in this case). By default, Spring Boot uses a no-args constructor and setters to create and populate an object. This does not allow immutable objects, however.
If we want immutable objects, as is the case with Quote
, we need to add the @ConstructorBinding
annotation to tell Spring Boot to use the constructor.
Verifying Access to the Static Data
To test if our static data works as expected, we can create a simple integration test:
@SpringBootTest(
properties = { "spring.config.location = ./,file:./quotes.yml" }
)
class QuotesPropertiesTest {
@Autowired
private QuotesProperties quotesProperties;
@Test
void staticQuotesAreLoaded() {
assertThat(quotesProperties.getQuotes()).hasSize(2);
}
}
The most important part of this test is setting the spring.config.location
property to tell Spring Boot to pick up our quotes.yml
file.
Then, we can simply inject the QuotesProperties
bean and assert that it contains the quotes we expect.
Accessing the Static Data
Finally, having the QuotesProperties
bean in place and tested, we can now simply inject it into any other bean to do whatever we need with our quotes. For instance, we can build a scheduler that logs a random quote every 5 seconds:
@Configuration
@EnableScheduling
public class RandomQuotePrinter {
private static final Logger logger =
LoggerFactory.getLogger(RandomQuotePrinter.class);
private final Random random = new Random();
private final QuotesProperties quotesProperties;
public RandomQuotePrinter(QuotesProperties quotesProperties) {
this.quotesProperties = quotesProperties;
}
@Scheduled(fixedRate = 5000)
void printRandomQuote(){
int index = random.nextInt(quotesProperties.getQuotes().size());
Quote quote = quotesProperties.getQuotes().get(index);
logger.info("'{}' - {}", quote.getText(), quote.getAuthor());
}
}
Conclusion
With @ConfigurationProperties
, Spring Boot makes it easy to load configuration from external sources, especially from local configuration files. These files can contain custom complex data structures and thus are ideal for static data that we don’t want to maintain within our source code or the database.
You can find the code to this article on github.