Skip to content

Split View: Spring Boot 설정 파일 구조와 폴더 구조 완전 가이드: application.yml 마스터하기

|

Spring Boot 설정 파일 구조와 폴더 구조 완전 가이드: application.yml 마스터하기

목차

  1. 프로젝트 표준 폴더 구조
  2. 레이어드 아키텍처 vs 도메인 중심 구조
  3. application.yml 계층 구조
  4. 외부 설정 우선순위
  5. @ConfigurationProperties 활용
  6. 멀티 모듈 프로젝트 구조
  7. Kubernetes ConfigMap/Secret 연동
  8. 퀴즈

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

각 패키지별 역할

패키지역할포함 클래스
configSpring 설정 클래스 모음SecurityConfig, WebMvcConfig
controllerHTTP 요청 처리 (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는 다양한 소스에서 설정을 읽어오며, 우선순위가 높은 것이 낮은 것을 덮어씁니다.

우선순위 (높은 것이 낮은 것을 덮어씀)

  1. 커맨드라인 인자 (--server.port=9090)
  2. SPRING_APPLICATION_JSON 환경변수 (인라인 JSON)
  3. ServletConfig 초기화 파라미터
  4. ServletContext 초기화 파라미터
  5. JNDI 속성 (java:comp/env/)
  6. Java 시스템 프로퍼티 (System.getProperties())
  7. OS 환경변수
  8. 무작위 값 프로퍼티 소스 (`random.*)
  9. 프로파일별 외부 application 파일 (application-dev.yml — JAR 외부)
  10. 외부 application 파일 (application.yml — JAR 외부)
  11. 프로파일별 내부 application 파일 (application-dev.yml — JAR 내부)
  12. 내부 application 파일 (application.yml — JAR 내부)
  13. @PropertySource 애노테이션
  14. 기본값 (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

  1. Standard Project Folder Structure
  2. Layered Architecture vs Domain-Centric Structure
  3. application.yml Hierarchy
  4. External Configuration Priority
  5. Using @ConfigurationProperties
  6. Multi-Module Project Structure
  7. Kubernetes ConfigMap and Secret Integration
  8. 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

PackageResponsibilityContains
configSpring configuration classesSecurityConfig, WebMvcConfig
controllerHTTP request handling (Presentation Layer)@RestController classes
serviceBusiness logic (Business Layer)@Service classes
repositoryData access (Data Layer)@Repository, JpaRepository
domainDomain model@Entity, DTO, VO
exceptionException handling@ControllerAdvice, Custom Exceptions
securitySecurity componentsFilter, Provider, Handler
utilShared utilitiesStatic 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
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)

  1. Command-line arguments (--server.port=9090)
  2. SPRING_APPLICATION_JSON environment variable (inline JSON)
  3. ServletConfig init parameters
  4. ServletContext init parameters
  5. JNDI attributes (java:comp/env/)
  6. Java system properties (System.getProperties())
  7. OS environment variables
  8. Random value property source (random.*)
  9. Profile-specific external application files (application-dev.yml — outside JAR)
  10. External application file (application.yml — outside JAR)
  11. Profile-specific packaged application files (application-dev.yml — inside JAR)
  12. Packaged application file (application.yml — inside JAR)
  13. @PropertySource annotations
  14. 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.