- Published on
Spring Boot Configuration Files and Project Structure Complete Guide: Mastering application.yml
- Authors

- Name
- Youngju Kim
- @fjvbn20031
Table of Contents
- Standard Project Folder Structure
- Layered Architecture vs Domain-Centric Structure
- application.yml Hierarchy
- External Configuration Priority
- Using @ConfigurationProperties
- Multi-Module Project Structure
- Kubernetes ConfigMap and Secret Integration
- Quiz
1. Standard Project Folder Structure
Spring Boot projects follow the Maven/Gradle standard directory layout. A consistent structure greatly improves team collaboration and maintainability.
my-project/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/app/
│ │ │ ├── AppApplication.java
│ │ │ ├── config/
│ │ │ │ ├── SecurityConfig.java
│ │ │ │ ├── WebMvcConfig.java
│ │ │ │ └── SwaggerConfig.java
│ │ │ ├── controller/
│ │ │ │ ├── UserController.java
│ │ │ │ └── ProductController.java
│ │ │ ├── service/
│ │ │ │ ├── UserService.java
│ │ │ │ └── UserServiceImpl.java
│ │ │ ├── repository/
│ │ │ │ └── UserRepository.java
│ │ │ ├── domain/
│ │ │ │ ├── User.java
│ │ │ │ └── dto/
│ │ │ │ ├── UserRequest.java
│ │ │ │ └── UserResponse.java
│ │ │ ├── exception/
│ │ │ │ ├── GlobalExceptionHandler.java
│ │ │ │ └── BusinessException.java
│ │ │ ├── security/
│ │ │ │ ├── JwtTokenProvider.java
│ │ │ │ └── JwtAuthFilter.java
│ │ │ └── util/
│ │ │ └── DateUtils.java
│ │ └── resources/
│ │ ├── application.yml
│ │ ├── application-dev.yml
│ │ ├── application-staging.yml
│ │ ├── application-prod.yml
│ │ ├── db/
│ │ │ └── migration/
│ │ │ ├── V1__create_users.sql
│ │ │ └── V2__add_user_index.sql
│ │ ├── static/
│ │ │ ├── css/
│ │ │ └── js/
│ │ ├── templates/
│ │ │ └── email/
│ │ │ └── welcome.html
│ │ └── messages/
│ │ ├── messages.properties
│ │ └── messages_en.properties
└── test/
├── java/
│ └── com/example/app/
│ ├── controller/
│ ├── service/
│ └── repository/
└── resources/
├── application-test.yml
└── data.sql
Package Responsibilities
| Package | Responsibility | Contains |
|---|---|---|
| config | Spring configuration classes | SecurityConfig, WebMvcConfig |
| controller | HTTP request handling (Presentation Layer) | @RestController classes |
| service | Business logic (Business Layer) | @Service classes |
| repository | Data access (Data Layer) | @Repository, JpaRepository |
| domain | Domain model | @Entity, DTO, VO |
| exception | Exception handling | @ControllerAdvice, Custom Exceptions |
| security | Security components | Filter, Provider, Handler |
| util | Shared utilities | Static helper methods |
2. Layered Architecture vs Domain-Centric Structure
Traditional Layered Structure (Function-Based Packages)
com/example/app/
├── controller/
│ ├── UserController.java
│ ├── OrderController.java
│ └── ProductController.java
├── service/
│ ├── UserService.java
│ ├── OrderService.java
│ └── ProductService.java
└── repository/
├── UserRepository.java
├── OrderRepository.java
└── ProductRepository.java
Pros: Easy to locate files by layer. Cons: Domain-scoped changes require modifications across multiple packages simultaneously.
Domain-Centric Structure (Domain-Based Packages)
com/example/app/
├── user/
│ ├── UserController.java
│ ├── UserService.java
│ ├── UserRepository.java
│ ├── User.java
│ └── UserDto.java
├── order/
│ ├── OrderController.java
│ ├── OrderService.java
│ └── Order.java
└── product/
├── ProductController.java
└── Product.java
Pros: High cohesion per domain; well-suited for independent deployment. Cons: Harder to see all files of the same layer at once.
Hexagonal Architecture Folder Structure
com/example/app/
└── user/
├── application/
│ ├── port/
│ │ ├── in/
│ │ │ └── CreateUserUseCase.java
│ │ └── out/
│ │ └── UserRepository.java
│ └── service/
│ └── CreateUserService.java
├── domain/
│ └── User.java
└── adapter/
├── in/
│ └── web/
│ └── UserController.java
└── out/
└── persistence/
└── UserJpaRepository.java
Hexagonal architecture isolates external dependencies (DB, message broker, etc.) behind interfaces, improving testability and replaceability.
3. application.yml Hierarchy
Base Structure and Profile Separation
Since Spring Boot 2.4, you can define multiple profiles in a single file using the --- separator.
# application.yml - Common configuration
spring:
application:
name: my-service
jackson:
default-property-inclusion: non_null
time-zone: America/New_York
logging:
level:
root: INFO
com.example: DEBUG
management:
endpoints:
web:
exposure:
include: health,info,metrics
---
# dev profile
spring:
config:
activate:
on-profile: dev
datasource:
url: jdbc:postgresql://localhost:5432/devdb
username: devuser
password: devpass
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
logging:
level:
org.hibernate.SQL: DEBUG
---
# staging profile
spring:
config:
activate:
on-profile: staging
datasource:
url: jdbc:postgresql://staging-db:5432/stagingdb
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: validate
---
# prod profile
spring:
config:
activate:
on-profile: prod
datasource:
url: jdbc:postgresql://prod-db:5432/proddb
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: validate
show-sql: false
logging:
level:
root: WARN
com.example: INFO
Separating Profiles into Distinct Files (Recommended for Large Projects)
resources/
├── application.yml # Common settings
├── application-dev.yml # Development environment
├── application-staging.yml # Staging environment
└── application-prod.yml # Production environment
Activating profiles:
# Activate via command line
java -jar app.jar --spring.profiles.active=prod
# Activate via environment variable
export SPRING_PROFILES_ACTIVE=prod
4. External Configuration Priority
Spring Boot reads configuration from many sources. Higher priority sources override lower ones.
Priority Order (Higher Overrides Lower)
- Command-line arguments (
--server.port=9090) SPRING_APPLICATION_JSONenvironment variable (inline JSON)ServletConfiginit parametersServletContextinit parameters- JNDI attributes (
java:comp/env/) - Java system properties (
System.getProperties()) - OS environment variables
- Random value property source (
random.*) - Profile-specific external application files (
application-dev.yml— outside JAR) - External application file (
application.yml— outside JAR) - Profile-specific packaged application files (
application-dev.yml— inside JAR) - Packaged application file (
application.yml— inside JAR) @PropertySourceannotations- Default properties (
SpringApplication.setDefaultProperties)
Overriding Settings via Environment Variables
# Dots in property names become underscores; names are uppercase
export SERVER_PORT=9090
export SPRING_DATASOURCE_URL=jdbc:postgresql://prod-db:5432/mydb
export SPRING_DATASOURCE_USERNAME=produser
Command-Line Argument Examples
java -jar app.jar \
--server.port=9090 \
--spring.profiles.active=prod \
--spring.datasource.url=jdbc:postgresql://prod-db:5432/mydb
5. Using @ConfigurationProperties
@ConfigurationProperties lets you bind related configuration into a type-safe class.
application.yml Configuration
app:
api-key: my-secret-key
timeout: 30s
allowed-hosts:
- api.example.com
- admin.example.com
retry:
max-attempts: 3
delay: 1s
mail:
host: smtp.example.com
port: 587
from: no-reply@example.com
@ConfigurationProperties Class
@ConfigurationProperties(prefix = "app")
@Validated
@Getter
@Setter
public class AppProperties {
@NotNull
private String apiKey;
private Duration timeout = Duration.ofSeconds(30);
private List<String> allowedHosts = new ArrayList<>();
private Retry retry = new Retry();
private Mail mail = new Mail();
@Getter
@Setter
public static class Retry {
private int maxAttempts = 3;
private Duration delay = Duration.ofSeconds(1);
}
@Getter
@Setter
public static class Mail {
private String host;
private int port = 587;
private String from;
}
}
Registering as a Bean
@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class AppApplication {
public static void main(String[] args) {
SpringApplication.run(AppApplication.class, args);
}
}
Or add @Component directly to the class:
@Component
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties { ... }
IDE Autocomplete Support (Metadata Generation)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
This dependency generates META-INF/spring-configuration-metadata.json at compile time, enabling IDE autocomplete in application.yml.
6. Multi-Module Project Structure
For large-scale projects, a multi-module structure offers better code reuse and boundary separation than a single module.
Maven Multi-Module Structure
my-project/ # Parent POM
├── pom.xml # Parent POM
├── my-api/ # REST API module
│ ├── pom.xml
│ └── src/main/java/
│ └── com/example/api/
├── my-domain/ # Domain/business logic module
│ ├── pom.xml
│ └── src/main/java/
│ └── com/example/domain/
├── my-infrastructure/ # DB, external API integration module
│ ├── pom.xml
│ └── src/main/java/
│ └── com/example/infra/
└── my-common/ # Shared utilities module
├── pom.xml
└── src/main/java/
└── com/example/common/
Parent pom.xml Example
<project>
<groupId>com.example</groupId>
<artifactId>my-project</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>my-common</module>
<module>my-domain</module>
<module>my-infrastructure</module>
<module>my-api</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>my-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
Managing Inter-Module Dependencies
my-api → my-domain → my-common
my-api → my-infrastructure → my-domain
Circular dependencies cause compilation failures. Extract shared interfaces into a dedicated module to prevent cycles.
7. Kubernetes ConfigMap and Secret Integration
Injecting Configuration via ConfigMap (Environment Variables)
# kubernetes/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-config
data:
SPRING_PROFILES_ACTIVE: 'prod'
SERVER_PORT: '8080'
SPRING_DATASOURCE_URL: 'jdbc:postgresql://db-service:5432/mydb'
# kubernetes/deployment.yaml
spec:
containers:
- name: my-app
envFrom:
- configMapRef:
name: my-app-config
- secretRef:
name: my-app-secret
Injecting Sensitive Data via Secrets
# Create a Secret
kubectl create secret generic my-app-secret \
--from-literal=SPRING_DATASOURCE_USERNAME=produser \
--from-literal=SPRING_DATASOURCE_PASSWORD=secretpass \
--from-literal=JWT_SECRET=myJwtSecretKey
Volume Mount Approach (File Injection)
Mounting a ConfigMap as a file lets you manage the entire application.yml externally.
# kubernetes/deployment.yaml
spec:
volumes:
- name: config-volume
configMap:
name: my-app-config-files
containers:
- name: my-app
volumeMounts:
- name: config-volume
mountPath: /app/config
env:
- name: SPRING_CONFIG_LOCATION
value: 'classpath:/,file:/app/config/'
spring-cloud-kubernetes Integration
Spring Cloud Kubernetes enables dynamic configuration reload on ConfigMap changes without application restart.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
</dependency>
spring:
cloud:
kubernetes:
config:
enabled: true
name: my-app-config
namespace: default
reload:
enabled: true
mode: event
period: 15000
8. Quiz
Q1. Why does Spring Boot give external configuration higher priority than settings inside the JAR?
Answer: To allow configuration changes in production environments without rebuilding the application.
Explanation: Spring Boot's external configuration priority follows the 12-Factor App principles. External application.yml files, environment variables, and command-line arguments take precedence over bundled settings. This allows the same JAR to be deployed across development, staging, and production environments with only the configuration differing per environment.
Q2. Why use @Validated together with @ConfigurationProperties?
Answer: To validate configuration values at application startup and prevent runtime errors caused by invalid configuration.
Explanation: Combining @Validated with JSR-303 Bean Validation annotations (@NotNull, @Min, @Max, etc.) causes Spring Boot to validate configuration values at startup. Missing required settings or out-of-range values immediately throw a BindValidationException, providing fast feedback before the application reaches production.
Q3. How do you prevent circular dependencies in a multi-module project?
Answer: Extract shared interfaces or common domain models into a dedicated common/shared module, and maintain unidirectional dependency flows.
Explanation: If module A depends on module B and module B depends on module A, compilation fails. Extracting shared interfaces or DTOs both modules need into a common module creates a unidirectional structure where both A and B depend on common, breaking the cycle.
Q4. How do you dynamically reload application configuration in Kubernetes without redeployment?
Answer: Use spring-cloud-starter-kubernetes-client-config and set spring.cloud.kubernetes.reload.enabled=true.
Explanation: Spring Cloud Kubernetes provides two reload modes. The event mode detects Kubernetes API events and reacts immediately to ConfigMap changes. The polling mode periodically polls for changes. Beans annotated with @RefreshScope are automatically re-initialized when the ConfigMap changes.
Q5. What is the purpose of spring.config.activate.on-profile in application.yml?
Answer: It specifies that a configuration block applies only when a specific profile is active.
Explanation: Since Spring Boot 2.4, a single application.yml can contain multiple document blocks separated by ---. Adding spring.config.activate.on-profile to a block restricts it to apply only when the specified profile is active. The older spring.profiles syntax is deprecated; spring.config.activate.on-profile is the recommended replacement.