Split View: Spring Boot 설정 파일 구조와 폴더 구조 완전 가이드: application.yml 마스터하기
Spring Boot 설정 파일 구조와 폴더 구조 완전 가이드: application.yml 마스터하기
목차
- 프로젝트 표준 폴더 구조
- 레이어드 아키텍처 vs 도메인 중심 구조
- application.yml 계층 구조
- 외부 설정 우선순위
- @ConfigurationProperties 활용
- 멀티 모듈 프로젝트 구조
- Kubernetes ConfigMap/Secret 연동
- 퀴즈
1. 프로젝트 표준 폴더 구조
Spring Boot 프로젝트는 Maven/Gradle 표준 디렉터리 레이아웃을 따릅니다. 일관된 구조는 팀 협업과 유지보수성을 크게 향상시킵니다.
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_ko.properties
└── test/
├── java/
│ └── com/example/app/
│ ├── controller/
│ ├── service/
│ └── repository/
└── resources/
├── application-test.yml
└── data.sql
각 패키지별 역할
| 패키지 | 역할 | 포함 클래스 |
|---|---|---|
| config | Spring 설정 클래스 모음 | SecurityConfig, WebMvcConfig |
| controller | HTTP 요청 처리 (Presentation Layer) | @RestController 클래스 |
| service | 비즈니스 로직 (Business Layer) | @Service 클래스 |
| repository | 데이터 접근 (Data Layer) | @Repository, JpaRepository |
| domain | 도메인 모델 | @Entity, DTO, VO |
| exception | 예외 처리 | @ControllerAdvice, Custom Exception |
| security | 보안 관련 컴포넌트 | Filter, Provider, Handler |
| util | 공통 유틸리티 | Static helper 메서드 |
2. 레이어드 아키텍처 vs 도메인 중심 구조
전통적 레이어 구조 (기능별 패키지)
com/example/app/
├── controller/
│ ├── UserController.java
│ ├── OrderController.java
│ └── ProductController.java
├── service/
│ ├── UserService.java
│ ├── OrderService.java
│ └── ProductService.java
└── repository/
├── UserRepository.java
├── OrderRepository.java
└── ProductRepository.java
장점: 레이어별로 파일을 찾기 쉬움 단점: 도메인 단위로 변경 시 여러 패키지를 동시에 수정해야 함
도메인 중심 구조 (도메인별 패키지)
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
장점: 도메인별 응집도가 높고 독립 배포에 유리 단점: 같은 레이어의 파일을 한 번에 보기 어려움
헥사고날 아키텍처 폴더 구조
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
헥사고날 아키텍처는 외부 의존성(DB, 메시지 브로커 등)을 인터페이스로 격리하여 테스트 용이성과 교체 가능성을 높입니다.
3. application.yml 계층 구조
기본 구조와 Profile 분리
Spring Boot 2.4 이후에는 단일 파일에서 --- 구분자로 여러 프로파일을 정의할 수 있습니다.
# application.yml - 공통 설정
spring:
application:
name: my-service
jackson:
default-property-inclusion: non_null
time-zone: Asia/Seoul
logging:
level:
root: INFO
com.example: DEBUG
management:
endpoints:
web:
exposure:
include: health,info,metrics
---
# dev 프로파일
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 프로파일
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 프로파일
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
별도 파일로 Profile 분리 (권장 방식)
대규모 프로젝트에서는 파일을 분리하면 관리가 더 쉬워집니다.
resources/
├── application.yml # 공통 설정
├── application-dev.yml # 개발 환경
├── application-staging.yml # 스테이징 환경
└── application-prod.yml # 운영 환경
프로파일 활성화 방법:
# 커맨드라인으로 활성화
java -jar app.jar --spring.profiles.active=prod
# 환경변수로 활성화
export SPRING_PROFILES_ACTIVE=prod
4. 외부 설정 우선순위
Spring Boot는 다양한 소스에서 설정을 읽어오며, 우선순위가 높은 것이 낮은 것을 덮어씁니다.
우선순위 (높은 것이 낮은 것을 덮어씀)
- 커맨드라인 인자 (
--server.port=9090) SPRING_APPLICATION_JSON환경변수 (인라인 JSON)ServletConfig초기화 파라미터ServletContext초기화 파라미터- JNDI 속성 (
java:comp/env/) - Java 시스템 프로퍼티 (
System.getProperties()) - OS 환경변수
- 무작위 값 프로퍼티 소스 (`random.*)
- 프로파일별 외부 application 파일 (
application-dev.yml— JAR 외부) - 외부 application 파일 (
application.yml— JAR 외부) - 프로파일별 내부 application 파일 (
application-dev.yml— JAR 내부) - 내부 application 파일 (
application.yml— JAR 내부) @PropertySource애노테이션- 기본값 (
SpringApplication.setDefaultProperties)
환경변수를 이용한 설정 오버라이드
# 프로퍼티명의 점(.)은 밑줄(_)로, 대문자로 변환
export SERVER_PORT=9090
export SPRING_DATASOURCE_URL=jdbc:postgresql://prod-db:5432/mydb
export SPRING_DATASOURCE_USERNAME=produser
커맨드라인 인자 예시
java -jar app.jar \
--server.port=9090 \
--spring.profiles.active=prod \
--spring.datasource.url=jdbc:postgresql://prod-db:5432/mydb
5. @ConfigurationProperties 활용
@ConfigurationProperties는 관련 설정을 타입 안전한 방식으로 묶어 관리할 수 있게 해줍니다.
application.yml 설정
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 클래스
@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;
}
}
Bean 등록
@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class AppApplication {
public static void main(String[] args) {
SpringApplication.run(AppApplication.class, args);
}
}
또는 클래스에 직접 @Component 추가:
@Component
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties { ... }
IDE 자동완성 지원 (메타데이터 생성)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
이 의존성을 추가하면 컴파일 시 META-INF/spring-configuration-metadata.json이 생성되어 IDE에서 application.yml 자동완성이 동작합니다.
6. 멀티 모듈 프로젝트 구조
대규모 프로젝트에서는 단일 모듈보다 멀티 모듈 구조가 코드 재사용과 경계 분리에 유리합니다.
Maven 멀티 모듈 구조
my-project/ # Parent POM
├── pom.xml # Parent POM
├── my-api/ # REST API 모듈
│ ├── pom.xml
│ └── src/main/java/
│ └── com/example/api/
├── my-domain/ # 도메인/비즈니스 로직 모듈
│ ├── pom.xml
│ └── src/main/java/
│ └── com/example/domain/
├── my-infrastructure/ # DB, 외부 API 연동 모듈
│ ├── pom.xml
│ └── src/main/java/
│ └── com/example/infra/
└── my-common/ # 공통 유틸리티 모듈
├── pom.xml
└── src/main/java/
└── com/example/common/
Parent pom.xml 예시
<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>
모듈 간 의존성 관리
my-api → my-domain → my-common
my-api → my-infrastructure → my-domain
순환 의존성이 발생하면 컴파일 오류가 발생합니다. 공통 인터페이스를 별도 모듈로 분리하여 순환을 방지합니다.
7. Kubernetes ConfigMap/Secret 연동
ConfigMap을 통한 설정 주입 (환경변수 방식)
# 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
Secret을 통한 민감 정보 주입
# 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
볼륨 마운트 방식 (파일로 주입)
ConfigMap을 파일로 마운트하면 application.yml 전체를 외부에서 관리할 수 있습니다.
# 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 연동
Spring Cloud Kubernetes를 사용하면 ConfigMap 변경 시 애플리케이션 재시작 없이 설정을 동적으로 갱신할 수 있습니다.
<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. 퀴즈
Q1. Spring Boot에서 외부 설정이 JAR 내부 설정보다 우선순위가 높은 이유는?
정답: 운영 환경에서 애플리케이션을 재빌드하지 않고 설정을 변경할 수 있도록 하기 위해서입니다.
설명: Spring Boot의 외부 설정 우선순위 체계는 12-Factor App 원칙을 따릅니다. JAR 외부의 application.yml이나 환경변수, 커맨드라인 인자가 JAR 내부 설정보다 높은 우선순위를 가집니다. 이를 통해 같은 JAR 파일을 개발/스테이징/운영 환경에 그대로 배포하고, 설정만 환경별로 다르게 주입할 수 있습니다.
Q2. @ConfigurationProperties를 사용할 때 @Validated를 함께 사용하는 이유는?
정답: 애플리케이션 시작 시 설정값의 유효성을 검사하여, 잘못된 설정으로 인한 런타임 오류를 사전에 방지하기 위해서입니다.
설명: @Validated와 JSR-303 Bean Validation 애노테이션(@NotNull, @Min, @Max 등)을 함께 사용하면, Spring Boot 애플리케이션 구동 시점에 설정값이 유효한지 검사합니다. 필수 설정이 누락되거나 값이 범위를 벗어나면 즉시 BindValidationException이 발생하여 빠른 피드백을 제공합니다.
Q3. 멀티 모듈 프로젝트에서 순환 의존성을 방지하는 방법은?
정답: 공통 인터페이스나 공유 도메인 모델을 별도의 공통 모듈(common/shared 모듈)로 분리하고, 의존성 방향을 단방향으로 유지합니다.
설명: A 모듈이 B 모듈에 의존하고, B 모듈이 A 모듈에 의존하는 순환 의존성이 발생하면 컴파일이 불가능합니다. 두 모듈이 공통으로 필요한 인터페이스나 DTO를 별도의 common 모듈로 분리하면, A와 B가 모두 common에 의존하는 단방향 구조를 만들 수 있습니다.
Q4. Kubernetes에서 application.yml 설정을 재배포 없이 동적으로 갱신하는 방법은?
정답: spring-cloud-kubernetes-client-config를 사용하고 spring.cloud.kubernetes.reload.enabled=true로 설정합니다.
설명: Spring Cloud Kubernetes는 ConfigMap의 변경을 감지하는 두 가지 모드를 제공합니다. event 모드는 Kubernetes API 이벤트를 감지하여 즉시 반응하고, polling 모드는 주기적으로 ConfigMap을 확인합니다. @RefreshScope 애노테이션이 붙은 Bean은 ConfigMap 변경 시 자동으로 재초기화됩니다.
Q5. application.yml에서 spring.config.activate.on-profile의 역할은?
정답: 해당 설정 블록이 특정 프로파일이 활성화되었을 때만 적용되도록 지정합니다.
설명: Spring Boot 2.4부터 단일 application.yml 파일에서 --- 구분자로 여러 문서 블록을 나눌 수 있습니다. 각 블록에 spring.config.activate.on-profile을 지정하면 해당 프로파일이 활성화된 경우에만 그 블록의 설정이 적용됩니다. 이전 방식인 spring.profiles는 더 이상 권장하지 않으며 spring.config.activate.on-profile을 사용해야 합니다.
Spring Boot Configuration Files and Project Structure Complete Guide: Mastering application.yml
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.