Skip to content
Published on

Spring Boot Configuration Files and Project Structure Complete Guide: Mastering application.yml

Authors

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.