Skip to content
Home Java Spring Boot application.properties Explained — A Complete Beginner's Guide

Spring Boot application.properties Explained — A Complete Beginner's Guide

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Spring Boot → Topic 1 of 15
Master Spring Boot configuration with this deep-dive into application.
🧑‍💻 Beginner-friendly — no prior Java experience needed
In this tutorial, you'll learn
Master Spring Boot configuration with this deep-dive into application.
  • application.properties lives in src/main/resources and is loaded by Spring Boot at startup by convention — no registration required. Defining spring.datasource.url in this file triggers full auto-configuration of the DataSource, connection pool, and JPA stack.
  • Use @Value("${key:default}") for single property injection and always include the colon-default fallback — without it, a missing property crashes the application at startup with a generic error message that does not name the missing key clearly.
  • @ConfigurationProperties with @Validated is the production standard for grouped settings — it gives you a typed POJO, Relaxed Binding for all naming conventions, JSR-303 validation with clear field-specific startup errors, and IDE autocomplete with the configuration-processor dependency.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer
  • application.properties is the externalized instruction sheet Spring Boot reads at startup — separate logic from environment without recompiling
  • Spring loads properties in priority order: command-line args > OS env vars > profile-specific files > base application.properties
  • @Value("${key:default}") injects single properties with a fallback — without the colon default, a missing key crashes the app at startup with no grace
  • @ConfigurationProperties groups related settings into a type-safe POJO with Relaxed Binding — kebab-case, snake_case, and UPPER_SNAKE_CASE all map to camelCase fields automatically
  • Profile files (application-prod.properties) overlay the base file — only conflicting keys are overridden, everything else is inherited
  • Add @Validated to your @ConfigurationProperties class to fail-fast at startup with clear error messages when required properties are missing or malformed
  • spring.config.import (Spring Boot 2.4+) replaces spring.config.additional-location for importing external config files and Vault/Config Server integration
  • The biggest mistake: hardcoding spring.profiles.active=prod in the base file — your CI pipeline deploys prod settings everywhere and wipes test data
🚨 START HERE
Configuration Debug Cheat Sheet — Commands That Save Hours
Real commands for debugging Spring Boot configuration issues in production.
🟡Need to see what properties are actually loaded at runtime
Immediate ActionHit the Actuator env endpoint to see all resolved properties and their sources
Commands
curl -s http://localhost:8080/actuator/env | jq '.propertySources[] | select(.name | contains("application"))'
curl -s http://localhost:8080/actuator/env/server.port | jq .
Fix NowIf the property shows a different source than expected — systemEnvironment instead of applicationConfig — an environment variable is overriding your properties file. The source name tells you exactly where the winning value came from.
🟡Need to verify which profile is active on a running instance
Immediate ActionQuery the Actuator env endpoint for active profiles
Commands
curl -s http://localhost:8080/actuator/env | jq '.activeProfiles'
printenv SPRING_PROFILES_ACTIVE
Fix NowIf no profile is active, the base application.properties is the only file being loaded. If the wrong profile is listed, check whether spring.profiles.active was hardcoded in the base file — that overrides the environment variable in some loading scenarios.
🟡YAML file causing parse errors or wrong values at startup
Immediate ActionCheck for tab characters and validate the YAML structure before Spring touches it
Commands
cat -A src/main/resources/application.yml | grep '^I'
python3 -c "import yaml; yaml.safe_load(open('src/main/resources/application.yml'))"
Fix NowReplace all tabs with 2 spaces: sed -i 's/\t/ /g' src/main/resources/application.yml — then re-validate with the python command to confirm no remaining parse errors.
🟡Database connection failing — need to verify the resolved datasource URL
Immediate ActionCheck what Spring actually resolved for the datasource URL and which source provided it
Commands
curl -s http://localhost:8080/actuator/env/spring.datasource.url | jq '.property'
curl -s http://localhost:8080/actuator/health/db | jq .
Fix NowThe response includes both the value and the source (applicationConfig:[prod], systemEnvironment, etc.). If the URL points to the wrong host, you now know exactly which configuration source to fix — not just that it is wrong.
Production IncidentThe Dev Database Wipe — Hardcoded Profile in Base ConfigA developer hardcoded spring.profiles.active=prod in application.properties and committed it. The CI pipeline deployed to staging with production database settings, truncating two weeks of QA test data.
SymptomQA team reported all test data was gone from the staging database. Integration tests failed with empty result sets. The staging environment was connecting to the production RDS instance instead of the staging database.
AssumptionThe team assumed the staging environment's own application-staging.properties was being loaded because SPRING_PROFILES_ACTIVE=staging was correctly set on the server. They spent two hours checking the staging server configuration before looking at the base properties file.
Root causespring.profiles.active=prod was hardcoded in the base application.properties file. The base file is loaded before environment variables are fully processed in the bean initialization lifecycle. A JPA migration script ran against the production datasource URL defined in application-prod.properties because the prod datasource bean was created from the hardcoded profile activation before the environment variable override took effect. The staging datasource was never instantiated.
FixRemoved spring.profiles.active from application.properties entirely. Set the profile exclusively via environment variable SPRING_PROFILES_ACTIVE on each server. Added a startup ApplicationListener that logs the active datasource URL and throws an IllegalStateException if the URL contains 'prod' and the host is not in the known production host list.
Key Lesson
Never set spring.profiles.active in the base application.properties — it overrides environment-level control and affects every environment that loads the base fileSet profiles exclusively via environment variables (SPRING_PROFILES_ACTIVE) or JVM arguments (-Dspring.profiles.active=prod)Add startup validation that logs the active datasource URL and fails loudly if a production URL appears on a non-production hostThe base file should contain only environment-agnostic defaults that are safe to activate in any environment
Production Debug GuideWhen Spring Boot configuration behaves unexpectedly, here is how to go from observable symptom to resolution.
Property change has no effect — app still uses old valueCheck for a trailing space on the key: 'server.port =8081' is parsed as key 'server.port ' (with a trailing space) which matches nothing — the port stays at 8080 with no error. Also check if an environment variable or command-line argument is overriding the file — both have higher priority than application.properties.
@ConfigurationProperties fields are all null at runtimeVerify the class has @Component, or @EnableConfigurationProperties(YourClass.class) is on a @Configuration class. Without one of these, Spring never processes the annotation. Also verify the prefix matches exactly — 'forge.bookstore' in the annotation must match 'forge.bookstore.page-size' in the properties file, not 'forge.bookstore.' with a trailing dot.
App starts on port 8080 despite setting server.port=9090 in application.propertiesCheck which profile is active — an application-prod.properties may override the port. Check for a SERVER_PORT environment variable set by Docker or Kubernetes: printenv SERVER_PORT. Environment variables always win over properties files. Hit /actuator/env/server.port to see which source is providing the value.
application-prod.properties not being loadedVerify the profile is active: check startup logs for 'The following 1 profile is active: prod'. Check the file exists in src/main/resources and was included in the JAR: jar tf your-app.jar | grep application-prod. If it is missing, the build step excluded it — check your Maven resources filtering configuration.
YAML configuration causes silent startup failure or wrong values with no clear errorCheck for tab characters — YAML requires spaces, never tabs. Run: cat -A application.yml | grep '^I' to find tabs. Also validate the YAML structure: python3 -c "import yaml; yaml.safe_load(open('src/main/resources/application.yml'))" — a parse error here means Spring will also fail to read the file.
Environment variable not being resolved — app shows literal ${VAR_NAME} in a config valueVerify the variable is set: printenv VAR_NAME. In Docker, ensure it is passed via -e or in the docker-compose environment block. In Kubernetes, verify it is in the pod spec's env section with the correct name. Note that Spring Boot converts environment variables to property keys by lowercasing and replacing underscores with dots — FORGE_DB_URL maps to forge.db.url.
@ConfigurationProperties validation is not failing fast — app starts with invalid config@Validated is missing from the @ConfigurationProperties class. Adding @Component alone does not activate validation. You must add @Validated explicitly alongside @ConfigurationProperties. Also ensure the hibernate-validator dependency is on the classpath — spring-boot-starter-validation includes it.

In professional software engineering, hardcoding is a cardinal sin. Embedding your database password or server port directly into Java source code creates a fragile system that requires a full recompile just to change a timeout value. This is where externalized configuration becomes critical.

Spring Boot's application.properties serves as the control center for your entire application. It separates the logic of your code from the environment it runs in. Whether deploying to a local Docker container or a Kubernetes cluster, the code stays the same — only the properties change.

Misconfiguring properties causes production failures that are silent and genuinely hard to diagnose. A misplaced space around the equals sign silently ignores your config change. A missing environment variable crashes the app at 2 AM when the fallback was never defined. Using tabs instead of spaces in a YAML file misparses your entire datasource configuration without throwing a startup error. These are not theoretical edge cases — they are the class of incidents that generate post-mortems.

This guide covers property injection patterns, environment profiles, the fail-fast validation strategy that catches misconfiguration at startup rather than at runtime, and the configuration management approaches used in production systems that handle secrets without committing them to version control.

What application.properties Actually Is and Where It Lives

Spring Boot follows a strict priority order for loading configuration. By default, it looks in src/main/resources/application.properties. When your build tool packages your app, this file is moved to the root of the classpath inside your JAR. Spring Boot finds it by convention — you do not register it anywhere.

The syntax is a flat key=value format. Spring uses these keys to auto-configure beans. Defining spring.datasource.url does not just store a string — it triggers Spring Boot's auto-configuration to create a DataSource bean, a HikariCP connection pool, and wire the entire JPA stack. You provide the parameters; Spring Boot writes the plumbing.

The priority chain matters more than most developers realize. A value in application.properties can be overridden by application-prod.properties, which can be overridden by an OS environment variable, which can be overridden by a JVM argument, which can be overridden by a command-line argument. Every environment variable your Kubernetes pod injects wins over anything in any file. Understanding this chain is what lets you debug configuration mismatches in under five minutes instead of four hours.

One critical detail the official docs bury: as of Spring Boot 2.4, the way additional config files are imported changed. The old spring.config.additional-location property still works, but the new spring.config.import is the forward-compatible approach and is required for Spring Cloud Config Server and Vault integration.

src/main/resources/application.properties · PROPERTIES
12345678910111213141516171819202122232425262728293031323334353637383940414243
# ── io.thecodeforge: Standard Server Config ───────────────────────
server.port=8081
server.servlet.context-path=/api/v1

# ── Application Identity ──────────────────────────────────────────
spring.application.name=forge-bookstore-service

# ── Database Connection (PostgreSQL) ──────────────────────────────
# ${DB_PASSWORD} resolves from OS environment variable.
# The :default_dev_pass fallback only applies locally — never in prod.
# In prod, DB_PASSWORD must be set explicitly or startup fails at datasource creation.
spring.datasource.url=jdbc:postgresql://localhost:5432/forge_db
spring.datasource.username=forge_admin
spring.datasource.password=${DB_PASSWORD:default_dev_pass}
spring.datasource.driver-class-name=org.postgresql.Driver

# ── Connection Pool (HikariCP) ────────────────────────────────────
# These defaults are tuned for a small service — adjust for your load profile.
# Too small: connection starvation under load.
# Too large: DB server thread exhaustion.
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=2
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000

# ── JPA / Hibernate ───────────────────────────────────────────────
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
# validate: checks schema matches entities at startup — use in prod
# update:  modifies schema automatically — never use in prod
# none:    no schema management — use in prod with external migrations (Flyway/Liquibase)
spring.jpa.hibernate.ddl-auto=validate

# ── Logging Configuration ─────────────────────────────────────────
logging.level.root=INFO
logging.level.io.thecodeforge=DEBUG

# ── Importing Additional Config (Spring Boot 2.4+) ────────────────
# Use spring.config.import to pull in extra config files or external sources.
# For Vault: spring.config.import=vault://
# For Config Server: spring.config.import=configserver:
# For local extra file: spring.config.import=optional:classpath:extra-config.properties
# 'optional:' prefix means do not fail if the file does not exist.
# spring.config.import=optional:classpath:extra-config.properties
▶ Output
INFO - Starting forge-bookstore-service on port 8081
INFO - HikariPool-1 - Starting with pool size 10
DEBUG - io.thecodeforge.repository: Executing SQL: SELECT * FROM books
INFO - Schema validation: 8 tables validated successfully
⚠ Never Commit Passwords to Git — Rotation is the Only Fix
Using ${DB_PASSWORD} tells Spring to resolve the value from an OS environment variable at runtime. This is the industry standard for keeping secrets out of source code. If a plaintext secret is accidentally committed, changing the value in a follow-up commit does not help — Git history is permanent and public repo scanners find leaked credentials within hours. The only correct response to an accidentally committed secret is to rotate it immediately, then add the environment variable pattern going forward.
📊 Production Insight
A developer committed spring.datasource.password=real_password_123 to a public repository. The commit was caught during code review and a fix commit was made the same hour. The Git history preserved the original plaintext value. An automated credential scanner found it in the public history within 48 hours. The database was accessed by an unauthorized actor before the team had rotated the password. The code change happened in 30 seconds. The incident response took three days.
🎯 Key Takeaway
application.properties lives in src/main/resources and is loaded by Spring Boot at startup by convention — no registration required. Defining spring.datasource.url triggers full auto-configuration of the DataSource, connection pool, and JPA layer. Never commit plaintext secrets — use ${ENV_VAR:local-dev-default} and treat every value ever committed to Git as permanently public. Use spring.config.import for additional config files and external sources like Vault or Config Server.

Injecting Properties Into Java Classes: @Value vs @ConfigurationProperties

Spring provides two mechanisms for getting property values into your Java code. @Value is the quick option for a single value. @ConfigurationProperties is the production option for any group of related settings.

@Value("${key:default}") injects a single property directly into a field. The colon-default syntax is not optional in production — without it, a missing property crashes the application at startup with IllegalArgumentException: Could not resolve placeholder. That crash at 2 AM during deployment is avoidable.

@ConfigurationProperties binds an entire property prefix to a typed Java class. It supports Relaxed Binding, which means forge.bookstore.page-size in the properties file maps to private int pageSize in your Java class automatically — no exact key matching required. It also supports JSR-303 validation via @Validated, which gives you explicit startup failures with clear messages when required properties are missing or out of range.

The IDE tooling difference is real and worth mentioning: if you add the spring-boot-configuration-processor dependency to your build, IntelliJ and VS Code generate autocomplete suggestions, type hints, and deprecation warnings for every field in your @ConfigurationProperties class. Without the processor, you are typing property keys blind. With it, you get the same IDE experience for custom properties that you get for Spring's built-in ones.

io/thecodeforge/bookstore/config/ForgeBookstoreProperties.java · JAVA
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
package io.thecodeforge.bookstore.config;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import lombok.Data;

/**
 * io.thecodeforge: Type-safe configuration with validation.
 *
 * @Validated: activates JSR-303 validation at startup.
 * If any constraint fails, the app refuses to start with a clear error message.
 * This is the fail-fast pattern — catch misconfiguration at startup, not at runtime.
 *
 * Relaxed Binding examples (all resolve to the same field):
 *   forge.bookstore.page-size=50        (kebab-case in .properties)
 *   forge.bookstore.page_size=50        (snake_case)
 *   FORGE_BOOKSTORE_PAGE_SIZE=50        (env var / UPPER_SNAKE_CASE)
 *   forge.bookstore.pageSize=50         (camelCase)
 */
@Data
@Component
@Validated
@ConfigurationProperties(prefix = "forge.bookstore")
public class ForgeBookstoreProperties {

    // Default values apply when the property is not defined in any config file.
    // These should be safe, conservative defaults — not production tuning values.
    @Min(value = 1, message = "forge.bookstore.page-size must be at least 1")
    private int pageSize = 10;

    @NotBlank(message = "forge.bookstore.default-currency must not be blank")
    private String defaultCurrency = "USD";

    // @Valid cascades validation into nested configuration objects.
    // Without @Valid here, the constraints inside Security are never evaluated.
    @Valid
    @NotNull(message = "forge.bookstore.security configuration block is required")
    private Security security = new Security();

    @Data
    public static class Security {

        // App refuses to start if forge.bookstore.security.api-key is missing or blank.
        // No more null API keys silently reaching production.
        @NotBlank(message = "forge.bookstore.security.api-key must be configured")
        private String apiKey;

        @Min(value = 5, message = "Session timeout must be at least 5 minutes")
        private int sessionTimeoutMinutes = 30;

        // Pattern validation — api-key must match expected format
        // Uncomment when you have a known format to enforce:
        // @Pattern(regexp = "^[A-Za-z0-9\\-]{32,64}$",
        //          message = "forge.bookstore.security.api-key must be 32-64 alphanumeric chars")
    }
}
▶ Output
// application.properties entries that bind to this class:
// forge.bookstore.page-size=50
// forge.bookstore.default-currency=EUR
// forge.bookstore.security.api-key=${BOOKSTORE_API_KEY}
// forge.bookstore.security.session-timeout-minutes=20

// If forge.bookstore.security.api-key is missing at startup:
// APPLICATION FAILED TO START
// Description: Binding to target forge.bookstore failed:
// Field error in object 'forge.bookstore' on field 'security.apiKey':
// rejected value [null]; codes [...]; default message
// [forge.bookstore.security.api-key must be configured]
//
// This error fires at startup — not on the first API call that uses the key.
// That is the difference between the fail-fast pattern and silent production failures.
💡Add spring-boot-configuration-processor for IDE Autocomplete
Add this to your pom.xml in the optional dependencies section: org.springframework.boot:spring-boot-configuration-processor with <optional>true</optional>. This generates metadata from your @ConfigurationProperties classes at compile time. IntelliJ and VS Code pick up the metadata and offer autocomplete, type hints, and documentation tooltips when editing application.properties or application.yml. It is the difference between typing 'forge.bookstore.' and seeing your custom properties suggested versus typing blind. The dependency is compile-scope only — it does not end up in your runtime JAR.
📊 Production Insight
A team used @Value("${payment.api.key}") without a colon-default fallback. The property existed in application-dev.properties and application-staging.properties but was accidentally omitted from application-prod.properties during a merge conflict resolution. The application crashed at startup on the production deployment with IllegalArgumentException. Zero-downtime deployment failed. The fix was one character — adding a colon default. The lesson: either always use ${key:DEFAULT} or switch to @ConfigurationProperties with @Validated and @NotBlank, which gives a far clearer error message about exactly which property is missing.
🎯 Key Takeaway
@Value is for single-property injection — always include the colon-default syntax ${key:fallback} to prevent startup crashes on missing properties. @ConfigurationProperties is the production standard for grouped settings — typed POJO, Relaxed Binding, @Validated support, and IDE autocomplete with the configuration-processor dependency. If you have three or more @Value annotations sharing a prefix, you should already be using @ConfigurationProperties.
Choosing Between @Value and @ConfigurationProperties
IfSingle property, no relationship to other properties
UseUse @Value("${key:default}") — fast and sufficient for standalone values
IfGroup of 3 or more related properties under the same prefix
UseUse @ConfigurationProperties — typed POJO, IDE autocomplete with configuration-processor, no scattered @Value annotations
IfNeed to validate property values and fail fast at startup with clear errors
UseUse @ConfigurationProperties with @Validated and JSR-303 constraints — @Value has no built-in validation
IfProperty name format varies between environments (kebab vs UPPER_SNAKE from env vars)
UseUse @ConfigurationProperties — Relaxed Binding handles all naming conventions automatically, @Value requires exact key match
IfNested configuration (e.g., security block within bookstore config)
UseUse @ConfigurationProperties with a nested static class — @Value cannot express hierarchy

Spring Boot Profiles — Environment-Specific Configuration Without Code Changes

Every application runs in multiple environments: your laptop, a CI runner, staging, and production. The database URLs differ. The log levels differ. The external service endpoints differ. Profiles let you express those differences in configuration files without touching the Java code or maintaining separate build artifacts.

Spring Boot loads files in this order: application.properties first as the baseline, then application-{profile}.properties as the overlay. Only keys that appear in both files are overridden — everything in the base file that is not mentioned in the profile file is inherited as-is. This means your profile files should be small and specific to what actually changes per environment.

Multiple profiles can be active simultaneously. SPRING_PROFILES_ACTIVE=prod,monitoring activates both the prod profile and a monitoring profile. Spring loads both overlay files. If the same key appears in both profile files, the last one wins in alphabetical order — which is why relying on multiple overlapping profiles for the same key is a maintenance trap.

The profile activation question is one of the most consistently asked in Spring Boot interviews: if the same property key appears in application.properties, application-prod.properties, AND an OS environment variable, what wins? The environment variable wins unconditionally. The full priority chain from highest to lowest is: command-line arguments, Java system properties (-D flags), OS environment variables, profile-specific property files, base application.properties.

src/main/resources/application-prod.properties · PROPERTIES
12345678910111213141516171819202122232425262728
# ── io.thecodeforge: Production Overrides ─────────────────────────
# This file contains ONLY what differs in production.
# Everything not listed here is inherited from application.properties.
# Keeping this file small is a feature — it makes prod differences explicit.

# Production runs on standard HTTPS port behind a load balancer
server.port=8080

# Production RDS instance — URL comes from environment variable for security
# SPRING_DATASOURCE_URL is set in the Kubernetes Secret and injected as env var
spring.datasource.url=${SPRING_DATASOURCE_URL}
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}

# Production connection pool — sized for actual production load
spring.datasource.hikari.maximum-pool-size=25
spring.datasource.hikari.minimum-idle=5

# Schema validation (not creation) — production schema is managed by Flyway
spring.jpa.hibernate.ddl-auto=validate

# No SQL logging in production — performance cost and log volume are not acceptable
logging.level.root=WARN
logging.level.io.thecodeforge=INFO
spring.jpa.show-sql=false

# Stricter session timeout in production
forge.bookstore.security.session-timeout-minutes=15
▶ Output
INFO - The following 1 profile is active: "prod"
INFO - HikariPool-1 - Starting with pool size 25
INFO - Connected to: jdbc:postgresql://rds.thecodeforge.io:5432/prod_db
INFO - Schema validation: 8 tables validated successfully
⚠ Profile Activation Priority — The Interview Answer
If a property key appears in application.properties, application-prod.properties, AND an OS environment variable, the environment variable wins unconditionally. Full priority chain from highest to lowest: command-line arguments (--server.port=9090) > Java system properties (-Dserver.port=9090) > OS environment variables (SERVER_PORT=9090) > profile-specific files (application-prod.properties) > base application.properties. This chain is why SPRING_PROFILES_ACTIVE as an environment variable correctly overrides any profile set in a properties file — as long as you have not also hardcoded spring.profiles.active in the base file, which short-circuits this mechanism.
📊 Production Insight
A team needed to add a 'monitoring' profile alongside 'prod' to activate Actuator endpoints in production. They set SPRING_PROFILES_ACTIVE=prod,monitoring on the server. The monitoring profile file included spring.jpa.show-sql=true for debugging — a property they forgot to remove before deployment. Every SQL query in production was logged for 72 hours before anyone noticed the disk usage spike. The lesson: profile files need the same code review discipline as Java code. A property you add for debugging in one environment will reach production if it ends up in the wrong file.
🎯 Key Takeaway
Profile files overlay the base file — keep them small and specific to what actually changes per environment. Multiple active profiles are possible — if the same key appears in both, the last one loaded wins. Never hardcode spring.profiles.active in the base file — set it via SPRING_PROFILES_ACTIVE environment variable on each server. Profile files go through the same code review process as source code — debugging properties left in profile files reach production.

application.properties vs application.yml — Syntax, Trade-offs, and Failure Modes

Both formats configure the same Spring Boot beans. The properties file is a flat key=value list. The YAML file is a hierarchical structure that eliminates prefix repetition at the cost of indentation sensitivity. Neither is objectively better — each has a specific failure mode that the other does not.

The properties file failure mode: almost none. A trailing space on a key makes it unrecognizable, but the app starts and you notice the value is wrong. A missing equals sign causes a parse error with a clear line number. It is hard to silently corrupt a properties file.

The YAML failure mode: silent misconfiguration. A tab character instead of two spaces causes the YAML parser to misparse the structure. Properties end up under the wrong parent key. Spring may create a bean with a null URL. The app starts, no parse error is thrown, and every database call fails at runtime. This class of failure is significantly harder to diagnose than a startup crash.

YAML has one genuine superpower: multi-document support. Using --- as a separator, you can define multiple Spring profiles in a single application.yml file. In Spring Boot 2.4 and later, use spring.config.activate.on-profile inside the document block. This is a YAML-only feature — you cannot do it with properties files.

The practical advice: use properties files for simple, flat configuration where correctness matters more than readability. Use YAML for complex nested configuration in microservices where the visual hierarchy genuinely helps. If you choose YAML, add a CI linting step that catches tab characters before they reach any server.

src/main/resources/application.yml · YAML
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
# io.thecodeforge: YAML configuration reference
# Equivalent to the application.properties example above, plus multi-profile demo.

spring:
  application:
    name: forge-bookstore-service
  datasource:
    url: jdbc:postgresql://localhost:5432/forge_db
    username: forge_admin
    # Environment variable resolution works identically in YAML
    password: ${DB_PASSWORD:default_dev_pass}
    hikari:
      maximum-pool-size: 10
      minimum-idle: 2
      connection-timeout: 30000
  jpa:
    hibernate:
      ddl-auto: validate
    database-platform: org.hibernate.dialect.PostgreSQLDialect
    show-sql: false

server:
  port: 8081
  servlet:
    context-path: /api/v1

logging:
  level:
    root: INFO
    io.thecodeforge: DEBUG

forge:
  bookstore:
    page-size: 10
    default-currency: USD
    security:
      session-timeout-minutes: 30

# ── Multi-document YAML: profile-specific overrides in one file ────
# The --- separator starts a new YAML document.
# spring.config.activate.on-profile is the Spring Boot 2.4+ syntax.
# Older spring.profiles syntax is deprecated as of 2.4do not use it.
---
spring:
  config:
    activate:
      on-profile: dev
server:
  port: 9090
logging:
  level:
    root: DEBUG
    org.hibernate.SQL: TRACE

---
spring:
  config:
    activate:
      on-profile: prod
server:
  port: 8080
spring:
  datasource:
    url: ${SPRING_DATASOURCE_URL}
    hikari:
      maximum-pool-size: 25
logging:
  level:
    root: WARN
▶ Output
# Active profile: dev
# server.port resolves to: 9090 (from dev document)
# logging.level.root resolves to: DEBUG (from dev document)
# spring.application.name resolves to: forge-bookstore-service (inherited from base)

# Active profile: prod
# server.port resolves to: 8080 (from prod document)
# spring.datasource.url resolves to: value of SPRING_DATASOURCE_URL env var
# spring.application.name resolves to: forge-bookstore-service (inherited from base)

# No profile active:
# All base document values apply — port 8081, DEBUG logging for io.thecodeforge
⚠ spring.profiles in Multi-Document YAML is Deprecated
Before Spring Boot 2.4, multi-document YAML used spring.profiles: dev to activate a document block. This syntax is deprecated as of 2.4 and removed in Spring Boot 3.0. The correct syntax is spring.config.activate.on-profile: dev inside the document block. If you are reading older blog posts or Stack Overflow answers about multi-document YAML, check whether they use the old syntax. It will silently not work on Spring Boot 3.x and will produce a deprecation warning on 2.4-2.7.
📊 Production Insight
A developer used tab characters throughout application.yml after copying configuration from a Slack message where tabs were auto-converted. The YAML parser misparsed the datasource block — the url field ended up at the root level instead of under spring.datasource. Spring Boot created a DataSource bean with a null URL. The application started without any startup error. The first database call threw NullPointerException on what appeared to be a correctly configured system. The fix: run cat -A application.yml | grep '^I' and replace all tabs with spaces. Add this check to your CI lint step so it never reaches any server again.
🎯 Key Takeaway
Properties files are difficult to silently corrupt — YAML can fail silently in ways that look like application bugs. Use YAML for complex nested configurations where hierarchy helps readability. Use multi-document YAML with spring.config.activate.on-profile (not the deprecated spring.profiles syntax) for profile-specific blocks. Always add a tab-detection check to CI — one tab character is enough to misparse an entire configuration file.

Secrets Management and External Configuration: Beyond the Properties File

For applications beyond the hobby scale, storing secrets in properties files — even with environment variable placeholders — is not sufficient. Rotating a database password means updating an environment variable on every host, restarting every instance, and hoping nothing was cached. A secret manager like HashiCorp Vault, AWS Secrets Manager, or Spring Cloud Config Server provides centralized secret storage, automatic rotation, audit logs, and fine-grained access control.

Spring Boot 2.4 introduced spring.config.import as the standardized way to pull configuration from external sources. A single line in application.properties or application.yml pulls the entire secret set from Vault or Config Server at startup. If the external source is unreachable and the import is not marked optional, the application refuses to start — which is exactly the fail-fast behavior you want rather than starting with missing secrets.

For teams not ready for Vault or Config Server, Jasypt (Java Simplified Encryption) provides a middle ground: properties files that store AES-encrypted values instead of plaintext. The encryption key is injected at runtime via environment variable. This is not equivalent security to Vault, but it prevents credentials from being readable in any file committed to version control or stored on disk in plaintext.

The environment variable approach used throughout this guide is the minimum acceptable standard for production. The approaches in this section are what teams with actual security requirements implement after the environment variable baseline is working.

src/main/resources/application.properties · PROPERTIES
1234567891011121314151617181920212223242526272829303132333435
# ── io.thecodeforge: External Configuration Import Examples ───────

# --- Spring Cloud Config Server ---
# Imports all properties from Config Server at startup.
# Config Server URL is typically set via SPRING_CONFIG_URI env var.
# If Config Server is unreachable, startup fails immediately (fail-fast).
spring.config.import=configserver:

# --- HashiCorp Vault ---
# Imports secrets from Vault path secret/forge-bookstore.
# Requires spring-cloud-starter-vault-config dependency.
# spring.config.import=vault://
# spring.cloud.vault.uri=http://vault.internal:8200
# spring.cloud.vault.token=${VAULT_TOKEN}
# spring.cloud.vault.kv.enabled=true
# spring.cloud.vault.kv.default-context=forge-bookstore

# --- Optional file import ---
# Import an additional file if it exists — do not fail if absent.
# Useful for local developer overrides that are .gitignored.
# spring.config.import=optional:file:./local-override.properties

# --- AWS Parameter Store (Spring Cloud AWS) ---
# spring.config.import=aws-parameterstore:
# spring.cloud.aws.parameterstore.enabled=true

# ── Jasypt Encryption (middle-ground secret management) ───────────
# Encrypted values are stored as ENC(encryptedString).
# The encryption key is injected at runtime — never stored in the file.
# jasypt.encryptor.password=${JASYPT_ENCRYPTOR_PASSWORD}
#
# Example encrypted datasource password:
# spring.datasource.password=ENC(Gb3V+Kj7Mc2VzXP4q8Rn5A==)
# Jasypt decrypts this at startup using the key from JASYPT_ENCRYPTOR_PASSWORD.
# Without the key, the encrypted string is meaningless — safe to commit.
▶ Output
# With spring.config.import=configserver: active:
INFO - Fetching config from server at: http://config-server:8888
INFO - Located environment: name=forge-bookstore-service, profiles=[prod], label=main
INFO - 12 properties loaded from Config Server
INFO - Starting forge-bookstore-service with config from Config Server

# If Config Server is unreachable and import is NOT optional:
APPLICATION FAILED TO START
Description: Could not locate PropertySource and the resource is not optional
Action: Check that Config Server is accessible at http://config-server:8888
⚠ spring.config.import vs spring.config.additional-location
Before Spring Boot 2.4, external config files were loaded via spring.config.additional-location. This property still works in 2.4+ for backward compatibility, but spring.config.import is the replacement and is required for Config Server, Vault, and AWS Parameter Store integration. The key behavioral difference: additional-location only works with files. config.import works with files, URL-based sources, and registered ConfigDataLoader implementations (Vault, Config Server, AWS). Use spring.config.import in all new code.
📊 Production Insight
A team moved from environment variable secrets to Vault integration. During the migration, they marked the Vault import as optional: spring.config.import=optional:vault:// so the application could start locally without Vault access. Six months later, the Vault token expired in production. Because the import was optional, the application started successfully with zero secrets loaded. The database password was null. Every request failed at the query level, not at startup. The fix: remove optional: from the Vault import in production. Optional imports are for local development only — in production, if your secret source is unavailable, you want a startup crash, not a running application with no credentials.
🎯 Key Takeaway
Environment variable secrets are the minimum acceptable standard — not the destination. spring.config.import is the Spring Boot 2.4+ mechanism for external configuration sources: Config Server, Vault, AWS Parameter Store. Mark external imports as optional: only for local development — in production, an unreachable secret source should prevent startup, not allow it. Jasypt provides encrypted properties as a middle ground for teams not yet running a secret manager.
🗂 application.properties vs application.yml
Both formats configure the same Spring Boot beans — the difference is syntax, readability, and failure modes.
Feature / Aspectapplication.propertiesapplication.yml
Format styleFlat key=value pairs, one per line. spring.datasource.url=... repeated for every datasource key.Indented YAML hierarchy. spring.datasource is written once; url, username, password nest beneath it.
Readability — simple configExcellent — the format is immediately obvious to any developer regardless of YAML experience.Slightly more verbose for simple flat configs — the hierarchy adds keystrokes without adding clarity.
Readability — nested configGets repetitive fast. Five datasource keys means writing spring.datasource five times.Excellent — hierarchy is visual and matches the logical grouping of the settings.
Multiple profiles in one fileNot possible — you must use separate files: application-dev.properties, application-prod.properties.Yes, using the --- document separator with spring.config.activate.on-profile (Spring Boot 2.4+ syntax).
Risk of silent misconfigurationVery low — trailing space on key is detectable. Parse errors report a clear line number.Real risk — a tab character silently misparses the structure. Properties end up under wrong parent keys with no startup error.
Lists and arraysIndexed: my.list[0]=a, my.list[1]=b. Comma-separated: my.list=a,b,c also works for simple lists.Clean YAML list syntax: use hyphen-prefixed items under the key. More readable for multi-item lists.
IDE autocomplete supportFull support in IntelliJ and VS Code with spring-boot-configuration-processor on the classpath.Full support in IntelliJ and VS Code with spring-boot-configuration-processor on the classpath.
Default in Spring InitializrYes — generated by default when creating a new project.Optional — select during project setup on start.spring.io.
Recommended for beginnersYes — simpler syntax and far harder to break silently.Better once you understand indentation rules and have a tab-detection step in your workflow.
Spring Boot 2.4+ multi-profile syntaxNot applicable — profile activation is always per-file.Use spring.config.activate.on-profile. The old spring.profiles syntax is deprecated in 2.4 and removed in Spring Boot 3.0.
Team preference trendCommon in mature codebases and teams prioritizing configuration correctness over visual hierarchy.Increasingly preferred in new microservice projects where nested configuration is the norm.

🎯 Key Takeaways

  • application.properties lives in src/main/resources and is loaded by Spring Boot at startup by convention — no registration required. Defining spring.datasource.url in this file triggers full auto-configuration of the DataSource, connection pool, and JPA stack.
  • Use @Value("${key:default}") for single property injection and always include the colon-default fallback — without it, a missing property crashes the application at startup with a generic error message that does not name the missing key clearly.
  • @ConfigurationProperties with @Validated is the production standard for grouped settings — it gives you a typed POJO, Relaxed Binding for all naming conventions, JSR-303 validation with clear field-specific startup errors, and IDE autocomplete with the configuration-processor dependency.
  • Profile files overlay the base file for conflicting keys — keep them small and specific to what actually changes per environment. Never hardcode spring.profiles.active in the base file. Set it via SPRING_PROFILES_ACTIVE environment variable on each server.
  • Spring Boot's property resolution hierarchy is: command-line arguments > Java system properties > OS environment variables > profile-specific files > base application.properties. Environment variables always win over properties files — this is by design, not a quirk.
  • YAML is indentation-sensitive — a single tab character silently misparses your entire configuration with no startup error. Properties files are nearly impossible to break silently. If you use YAML, add tab detection to your CI linting step.
  • spring.config.import (Spring Boot 2.4+) is the forward-compatible way to load external configuration from Config Server, Vault, and AWS Parameter Store. Mark imports as optional: only in local development — in production, an unreachable secret source should crash startup, not allow a running application with no credentials.
  • Add spring-boot-configuration-processor as an optional compile dependency when using @ConfigurationProperties — it generates IDE autocomplete metadata that catches property name typos before they become null-value debugging sessions.

⚠ Common Mistakes to Avoid

    Putting a trailing space on the key side of the equals sign
    Symptom

    Spring Boot silently ignores the key — 'server.port =8081' is parsed as key 'server.port ' (with trailing space) which matches no known property. The port stays at 8080 and your configuration change has no effect. No error is thrown, no warning is logged.

    Fix

    Remove all whitespace from around the key. 'key=value' is correct. Run grep ' =' src/main/resources/application.properties before committing to catch trailing spaces. Adding a properties linting step to CI catches this before it reaches any environment.

    Forgetting to enable @ConfigurationProperties scanning
    Symptom

    All fields in your @ConfigurationProperties class are null or their Java-defined defaults at runtime. No error at startup — the bean exists in the context but was never populated. Injection succeeds silently and every configured value is ignored.

    Fix

    Add @Component directly to your @ConfigurationProperties class (the simplest option), or add @EnableConfigurationProperties(YourClass.class) to a @Configuration class. Without one of these, Spring registers the class as a bean but never runs the binding logic that populates the fields.

    Hardcoding spring.profiles.active=prod in the base application.properties
    Symptom

    Every environment — local development, CI, staging, production — loads prod settings because the base file activates the prod profile before any environment variable can take effect. CI pipelines run database migrations against production. Staging environments connect to production databases. No error is thrown because the configuration is technically valid.

    Fix

    Remove spring.profiles.active from the base file entirely. Set it via environment variable SPRING_PROFILES_ACTIVE on each server, or pass it as a JVM argument at startup. The base file should contain only defaults that are safe in every environment.

    Using @Value without a colon-default fallback
    Symptom

    Application crashes at startup with IllegalArgumentException: Could not resolve placeholder 'my.property'. This happens when the property is missing from the active profile's configuration file — most commonly discovered during a production deployment at an inconvenient time.

    Fix

    Always use the colon-default syntax: @Value("${my.property:defaultValue}"). If no safe default exists, consider switching to @ConfigurationProperties with @Validated and @NotBlank — the error message will clearly name the missing property and fail at startup rather than at the injection site.

    Mixing application.properties and application.yml in the same project
    Symptom

    Confusion about which file is the source of truth. When the same key appears in both files, the .properties file takes precedence over .yml — but this precedence is not obvious and leads to debugging sessions where the yml change has no effect because the properties file is silently winning.

    Fix

    Pick one format for the project and delete the other. If both exist, Spring Boot loads both and .properties wins for conflicting keys. Document the chosen format in your project README to prevent a future developer from creating the other format.

    Missing spring-boot-configuration-processor dependency
    Symptom

    IntelliJ and VS Code show no autocomplete suggestions for custom properties defined via @ConfigurationProperties. Engineers type property keys manually, introduce typos, and spend time debugging null values that are simply misspelled property names. No compile error, no startup error — just a property that is never bound.

    Fix

    Add org.springframework.boot:spring-boot-configuration-processor as an optional compile dependency. This generates a META-INF/spring-configuration-metadata.json file at compile time that IDEs use for autocomplete, type hints, and documentation. The dependency is optional — it does not affect the runtime JAR.

    Using spring.profiles syntax in multi-document YAML (deprecated in 2.4, removed in 3.0)
    Symptom

    On Spring Boot 2.4+, profile-specific YAML document blocks using 'spring.profiles: dev' are silently ignored or produce a deprecation warning. The profile-specific overrides never apply. On Spring Boot 3.0+, startup fails with a configuration exception.

    Fix

    Replace 'spring.profiles: dev' with 'spring.config.activate.on-profile: dev' in each multi-document block. This is the correct syntax for Spring Boot 2.4 and later. Any blog post, Stack Overflow answer, or internal documentation using the old syntax predates 2.4 and should be updated.

    Marking external configuration imports as optional in production
    Symptom

    The Vault token expires or the Config Server is unreachable. Because the import is marked optional, the application starts with no secrets loaded. Database passwords are null. Every request fails at the query execution level rather than at startup. The application is technically running but completely non-functional.

    Fix

    Use optional: prefix only for local development imports where the external source is not available on developer machines. In production application-prod.properties or via environment-specific config, use non-optional imports: spring.config.import=vault:// or spring.config.import=configserver:. An unreachable secret source in production should prevent startup, not allow it.

Interview Questions on This Topic

  • QWhat is the difference between @Value and @ConfigurationProperties? When would you choose one over the other?Mid-levelReveal
    @Value injects a single property value directly into a field using @Value("${key}"). It is simple and fast for one-off values but has three practical limitations: it does not support grouping of related properties, it requires the key to match exactly (no Relaxed Binding), and it has no built-in validation — a missing property crashes with a generic IllegalArgumentException. @ConfigurationProperties binds an entire property prefix to a typed POJO. It supports Relaxed Binding — forge.bookstore.page-size, FORGE_BOOKSTORE_PAGE_SIZE, and forge.bookstore.pageSize all bind to the same field. It supports JSR-303 validation via @Validated, giving you explicit startup failures with field-specific error messages when required properties are missing. It also generates IDE autocomplete metadata via the configuration-processor dependency. Choose @Value for standalone, single-purpose properties where a default value is acceptable. Choose @ConfigurationProperties when you have three or more related properties under the same prefix, when you need validation, or when the property values come from environment variables with different naming conventions.
  • QHow does Spring Boot resolve properties? If the same key is in application.properties and a system environment variable, which one takes priority?Mid-levelReveal
    Spring Boot loads properties from multiple sources in a strict priority order. From highest to lowest: command-line arguments (--server.port=9090), Java system properties set via -D flags, OS environment variables, profile-specific property files (application-prod.properties), and finally the base application.properties inside the JAR. The environment variable wins over the properties file. A SERVER_PORT=9090 environment variable overrides server.port=8080 in application.properties unconditionally. This priority chain is why SPRING_PROFILES_ACTIVE as an environment variable correctly activates a profile even when the properties file contains a different value — as long as you have not hardcoded spring.profiles.active in the base file, which short-circuits this mechanism by loading a profile before the environment variable resolution phase for that property.
  • QA Spring Boot application is not picking up changes in application-prod.properties. What are the first three things you would check?Mid-levelReveal
    First: verify the profile is actually active. Check startup logs for 'The following 1 profile is active: prod'. If that line is absent or shows a different profile, check the SPRING_PROFILES_ACTIVE environment variable on the server: printenv SPRING_PROFILES_ACTIVE. Second: verify the file was included in the built JAR. Run jar tf your-app.jar | grep application-prod. If the file is absent from the JAR, the build step excluded it — check Maven resources filtering configuration or Gradle processResources task. Third: check whether a higher-priority source is overriding the property. An OS environment variable silently overrides the same key in application-prod.properties. Hit /actuator/env/{property.key} to see exactly which source is providing the current value and confirm whether the profile file's value is being loaded but overridden, or never loaded at all.
  • QExplain Relaxed Binding in Spring Boot and give an example of how it applies to configuration properties.Mid-levelReveal
    Relaxed Binding means Spring Boot maps property names in different naming conventions to the same Java field. For a @ConfigurationProperties class with prefix 'forge.bookstore' and a field named pageSize, all of the following bind to that field: forge.bookstore.page-size (kebab-case, preferred in properties files), forge.bookstore.page_size (snake_case), forge.bookstore.pageSize (camelCase), and FORGE_BOOKSTORE_PAGE_SIZE (upper snake case, the convention for OS environment variables). Relaxed Binding applies only to @ConfigurationProperties — @Value requires an exact key match and does not apply any binding rules. The practical implication: when a Kubernetes Secret injects FORGE_BOOKSTORE_PAGE_SIZE=50 as an environment variable, it automatically binds to the pageSize field in your @ConfigurationProperties class without any extra configuration. This is the mechanism that makes environment variable injection seamless regardless of naming convention differences between Java code and OS environment variables.
  • QHow can you use @Validated with @ConfigurationProperties to ensure your application fails to start if a required property is missing or malformed?SeniorReveal
    Add @Validated to your @ConfigurationProperties class alongside @Component. Then add JSR-303 annotations to individual fields: @NotBlank on strings that must not be empty or null, @Min(1) on integers that must be positive, @Email on fields expecting email addresses, and @Pattern(regexp="...") for custom format validation. For nested configuration objects, add @Valid on the nested field in the parent class — without @Valid, the constraints inside the nested class are never evaluated. When Spring Boot processes the configuration at startup, it validates each annotated field after binding. If any constraint fails, the application throws a BindException with a field-specific message and refuses to start. This is the fail-fast pattern: catch misconfiguration at startup rather than at the first runtime call that uses the bad value. You also need hibernate-validator on the classpath — spring-boot-starter-validation includes it. Without the validator dependency, @Validated is present but validation silently never runs.
  • QWhat changed about external configuration loading in Spring Boot 2.4, and what is the difference between spring.config.import and spring.config.additional-location?SeniorReveal
    Spring Boot 2.4 introduced spring.config.import as a replacement for spring.config.additional-location for importing external configuration sources. spring.config.additional-location works only with file-based sources — local files and classpath resources. It loads additional property files and adds them to the property source list. spring.config.import is more powerful: it works with files, URL-based sources, and registered ConfigDataLoader implementations. This includes Spring Cloud Config Server (configserver:), HashiCorp Vault (vault://), and AWS Parameter Store (aws-parameterstore:). Without spring.config.import, these integrations cannot pull properties at startup in the standard way. The behavioral difference also matters: an import without the optional: prefix causes startup to fail if the source is unreachable — this is the correct behavior in production. Adding optional: allows startup to continue without the import, which is useful for local development where Vault or Config Server is not running, but dangerous in production where missing secrets should prevent startup rather than allow a broken application to serve traffic.

Frequently Asked Questions

Where exactly does application.properties go in a Spring Boot project?

It goes in src/main/resources. When Maven or Gradle builds the project, everything in src/main/resources is copied to the root of the output JAR. Spring Boot knows to look there by convention — you do not register the file anywhere. If you need a developer-local override that should not be committed to Git, you can create application-local.properties in the same directory, add it to .gitignore, and activate it with SPRING_PROFILES_ACTIVE=local. Only properties that differ locally need to be in that file.

Can I have both application.properties and application.yml in the same project?

Technically yes, Spring Boot loads both. If the same key appears in both files, the .properties file takes precedence over .yml. This precedence is non-obvious and creates a class of debugging problems where a YAML change has no effect because the properties file is silently overriding it. Pick one format for your project, delete the other, and document the choice in your project README. The only exception is during a migration from one format to the other — but that migration should be a single commit that removes the old format entirely.

How do I use an environment variable inside application.properties?

Use ${VARIABLE_NAME} syntax directly: spring.datasource.url=${DB_URL}. Spring resolves this against OS environment variables at startup. You can add a fallback for local development: spring.datasource.url=${DB_URL:jdbc:h2:mem:testdb}. The fallback after the colon is used when the environment variable is not set. In production, the environment variable should always be set — if DB_URL is absent and you have no fallback, the application fails at startup, which is the correct behavior for a missing production credential.

Can I use Spring Boot Actuator to view my current properties at runtime?

Yes. Enable /actuator/env in your exposure list and hit the endpoint. It shows every resolved property value and, critically, which source provided it — applicationConfig:[prod], systemEnvironment, commandLineArgs, and so on. This is the fastest way to diagnose configuration mismatches: if the value is wrong, the source field tells you exactly which configuration layer to fix. Secure this endpoint with ADMIN role in your SecurityFilterChain — it can expose database URLs and other sensitive configuration paths to anyone who can reach it.

What happens if I use a tab instead of spaces in application.yml?

YAML requires spaces for indentation — tabs are not valid YAML syntax. A tab character causes the YAML parser to misparse the document structure. Depending on where the tab appears, properties end up under the wrong parent key, are silently ignored, or the entire document fails to parse. The particularly dangerous outcome is when the parser does not throw an error — the application starts, the datasource URL is null under the wrong key, and every database call fails at runtime rather than at startup. Always use 2-space indentation and run cat -A application.yml | grep '^I' to detect tabs before committing.

How do I set a profile without changing application.properties?

Three ways, from most to least preferred: (1) Set the environment variable SPRING_PROFILES_ACTIVE=prod on the server, in Docker via -e, or in Kubernetes via env in the pod spec — this is the standard for production deployments. (2) Pass it as a JVM argument: java -Dspring.profiles.active=prod -jar app.jar — useful for local testing of profile behavior. (3) Pass it as a command-line argument: java -jar app.jar --spring.profiles.active=prod — highest priority, overrides everything. Never set spring.profiles.active in the base application.properties file — it short-circuits environment variable control and affects every environment that loads the base file.

What is the difference between spring.config.import and spring.config.additional-location?

spring.config.additional-location (pre-2.4) loads additional file-based configuration sources and adds them to the property source list. It works only with files and classpath resources. spring.config.import (Spring Boot 2.4+) is the replacement and works with files, URL sources, and registered ConfigDataLoader implementations — including Spring Cloud Config Server (configserver:), HashiCorp Vault (vault://), and AWS Parameter Store. For any integration with external secret managers or configuration servers, spring.config.import is required. The optional: prefix controls fail-fast behavior: without it, startup fails if the source is unreachable — which is the correct production behavior.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

Next →Spring Boot Project Structure
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged