Stage 25: load a separate catalog.properties file
Learning path / Stage 25 of 37
Understand how an extra properties file enters the Spring Environment.
Download the complete Stage 25 project · Open the project guide
Before you start
Use JDK 21 or newer and Maven 3.9 or newer. Verify with java -version and mvn -version. Extract this project into its own folder; do not merge its files with another stage.
This lesson reuses the product catalog and Todo responsibilities from Stages 8–14. Only the feature described below is new. Read the ordered explanation before opening the complete files.
What we change and why
Reuse a setting already familiar from server.port
A property is a named value outside the Java implementation. Here catalog.heading changes displayed wording without editing a class.
Read one value with @Value
The placeholder names the property. The colon supplies Products as a fallback if the key is missing. A constructor receives the resolved String rather than the literal placeholder.
Distinguish Spring from Boot loading
Stage 24 uses SpringApplication, so Boot loads its conventional application.properties automatically. Stage 25 deliberately uses a plain Framework context and explicitly adds catalog.properties with @PropertySource. File names alone do not make a plain Framework context perform Boot’s configuration loading.
Load the extra file deliberately
@PropertySource adds catalog.properties to Spring’s Environment. It loads Java properties format by default. It does not automatically parse YAML. In Boot use its configuration-data support for YAML. The injected catalog.heading setting and CatalogSettings class remain the same as Stage 24; the loading mechanism is now explicit.
Run this example
Open a terminal in the extracted folder containing pom.xml. Run one command at a time.
mvn test
mvn spring-boot:runThis is a console lesson. It does not open a web port. Application.main starts a Spring context, calls the demonstration and closes the context. Read the terminal, not a browser. Spring Boot’s Maven plugin is used as a convenient launcher; these examples show Spring Framework directly.
Expected result
Heading: Edupoly ProductsObject identifiers and elapsed times can vary. Compare the relationship between values rather than copying an identifier.
Understand each complete file
Create the following files at exactly these paths, or use the ZIP. Source files include their imports. The test checks the behaviour described in the lesson.
pom.xml
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>4.1.1</version><relativePath/></parent>
<groupId>com.edupoly</groupId><artifactId>spring-stage-25-property-source</artifactId><version>1.0.0</version>
<properties><java.version>21</java.version></properties>
<dependencies>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies>
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
src/main/java/com/edupoly/lesson/AppConfig.java
package com.edupoly.lesson;
import org.springframework.context.annotation.*;
@Configuration @ComponentScan("com.edupoly.lesson")
@PropertySource("classpath:catalog.properties")
public class AppConfig { }
src/main/java/com/edupoly/lesson/CatalogSettings.java
package com.edupoly.lesson;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component public class CatalogSettings {
private final String heading;
public CatalogSettings(@Value("${catalog.heading:Products}") String heading) { this.heading = heading; }
public String heading() { return heading; }
}
src/main/java/com/edupoly/lesson/Lesson.java
package com.edupoly.lesson;
import org.springframework.stereotype.Component;
@Component public class Lesson {
private final CatalogSettings settings;
public Lesson(CatalogSettings settings) { this.settings = settings; }
public void run() { System.out.println("Heading: " + settings.heading()); }
}
src/main/resources/catalog.properties
catalog.heading=Edupoly Products
src/main/java/com/edupoly/lesson/Application.java
package com.edupoly.lesson;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Application {
public static void main(String[] args) throws Exception {
try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
context.getBean(Lesson.class).run();
}
}
}
src/test/java/com/edupoly/lesson/LessonTest.java
package com.edupoly.lesson;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
class LessonTest {
@Test void configuredHeadingReachesBean() {
try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
assertEquals("Edupoly Products", context.getBean(CatalogSettings.class).heading());
}
}
}
Try a failure, then correct it
Rename catalog.heading to catalog.title and restart: the fallback Products appears. Restore the original key.
Words you can now explain
| Word | Meaning in this example |
|---|---|
| @Value | Resolves a configured value into the constructor argument. |
| Property source | A named source of configuration values in Spring’s Environment. |
Practice assignment
Add a second setting, catalog.currency, and supply a fallback value.
Concepts used in this stage
Use the right column for a quick reminder, or open a concept above for its full explanation.