Skip to content

Properties & Profiles Interview Questions & Answers

16 questions Updated 2026-06-26 Share:

Externalized configuration in Spring Boot — property sources and precedence, @ConfigurationProperties vs @Value, profiles, relaxed binding, YAML, and managing secrets per environment.

Read the in-depth guideSpring Boot Configuration: Properties, Profiles, and Precedence(opens in new tab)
16 of 16

Externalized configuration means keeping settings (URLs, credentials, feature flags) outside compiled code so the same artifact runs in dev, test and prod by reading different values. Spring Boot pulls these values from many sources into one Environment.

@Service
public class MailService {
    // Value comes from application.properties, an env var, a CLI arg, etc.
    // The jar is identical across environments — only the source values change.
    public MailService(@Value("${app.mail.from}") String fromAddress) { }
}
# application.properties
[email protected]

The same built jar is promoted unchanged from staging to production; only the external values differ. This is a core principle of twelve-factor apps and the reason Spring Boot apps are so portable across environments and containers.

Rule of thumb: Never bake environment-specific values into code — read them from the Environment so one artifact runs everywhere.

Spring Boot merges many property sources into one Environment, and later sources override earlier ones. Knowing the order explains why "my property isn't taking effect."

Highest precedence (wins) ↓
1. Command-line args            --server.port=9000
2. SPRING_APPLICATION_JSON      (inline JSON env var / system prop)
3. OS environment variables     SERVER_PORT=9000
4. Java system properties       -Dserver.port=9000
5. Profile-specific files       application-prod.properties
6. application.properties/.yml  (the base file)
7. @PropertySource on @Configuration
8. Default properties           SpringApplication.setDefaultProperties(...)
Lowest precedence ↑
# A command-line arg beats everything, so this wins over any file:
java -jar app.jar --server.port=9000

(The exact list is longer — devtools, test properties, and config-data imports also slot in — but this is the practically important ordering.) The takeaway: command-line and environment variables override files, which is exactly what you want for containers.

Rule of thumb: When a value won't change, check precedence first — a CLI arg or env var higher in the list is almost always silently overriding your file.

Both read external values, but @Value injects one property at a time via SpEL, while @ConfigurationProperties binds a whole group of related properties to a typed object.

// @Value — fine for one or two values:
@Value("${app.mail.from}") String from;
@Value("${app.mail.retries:3}") int retries;     // ':3' is a default

// @ConfigurationProperties — preferred for a group of related settings:
@ConfigurationProperties(prefix = "app.mail")
public class MailProperties {
    private String from;
    private int retries = 3;                       // default in code
    private List<String> bccList;                  // structured types just work
    // getters/setters
}

@ConfigurationProperties gives you type safety, relaxed binding, nested objects and lists, validation (@Validated + JSR-380), and IDE metadata. @Value has no relaxed binding and can't bind structured types easily. Reserve @Value for one-off values; use @ConfigurationProperties for any cohesive config block.

Rule of thumb: A single value → @Value; a group of related settings → @ConfigurationProperties for type safety, validation and relaxed binding.

A profile is a named logical group of configuration and beans that is only active in certain environments. Profiles let one codebase behave differently in dev, test and prod.

@Service
@Profile("dev")                       // only created when 'dev' is active
public class InMemoryEmailService implements EmailService { }

@Service
@Profile("prod")                      // only created when 'prod' is active
public class SmtpEmailService implements EmailService { }
# Activate one or more profiles:
spring.profiles.active=prod

Profiles drive both bean selection (@Profile on components/@Bean methods) and property files (application-prod.properties loads only when prod is active). You can activate several at once (dev,debug) and combine with expressions like @Profile("prod & !legacy").

Rule of thumb: Use profiles for environment-shaped differences (which EmailService, which DB), not for fine-grained feature flags — those belong in properties.

Profiles can be activated from any property source, so the mechanism matches how you deploy — a CLI flag locally, an env var in a container.

# 1. Command-line argument (highest precedence):
java -jar app.jar --spring.profiles.active=prod

# 2. Environment variable (ideal for Docker/Kubernetes):
export SPRING_PROFILES_ACTIVE=prod

# 3. JVM system property:
java -Dspring.profiles.active=prod -jar app.jar
# 4. In application.properties (a default, easily overridden):
spring.profiles.active=dev

# 5. Add profiles without replacing active ones:
spring.profiles.include=metrics,tracing

In tests, use @ActiveProfiles("test"). Avoid hard-coding spring.profiles.active in the base application.properties for production values — set it externally so the same jar stays environment-agnostic.

Rule of thumb: Set the active profile outside the jar (env var or CLI) so the build artifact is identical across environments.

Files named application-{profile}.properties (or .yml) are loaded only when that profile is active, and they override the base application.properties.

# application.properties (base — always loaded)
app.cache.ttl=60
spring.datasource.url=jdbc:h2:mem:devdb

# application-prod.properties (loaded only when 'prod' is active)
app.cache.ttl=600
spring.datasource.url=jdbc:postgresql://db:5432/app

When prod is active, Spring loads the base file and the prod file, with prod values winning on conflicts. Multiple active profiles layer in their declaration order. This is the cleanest way to express "same keys, different values per environment" without if statements in code.

Rule of thumb: Put shared defaults in application.properties and only the differences in each application-{profile} file — keep the overrides minimal.

Relaxed binding lets a single @ConfigurationProperties field be set using several naming conventions, so the canonical kebab-case property maps cleanly to environment variables, system properties and YAML.

@ConfigurationProperties(prefix = "app.mail")
public class MailProperties {
    private String fromAddress; // canonical property: app.mail.from-address
}
All of these bind to fromAddress:
  app.mail.from-address   (kebab-case — canonical, use in .properties/.yml)
  app.mail.fromAddress    (camelCase)
  app.mail.from_address   (underscore)
  APP_MAIL_FROMADDRESS    (upper snake — how env vars MUST be written)

Relaxed binding is what makes SPRING_DATASOURCE_URL (an env var) set spring.datasource.url. Note it applies to @ConfigurationProperties, not to @Value, which is another reason to prefer the former.

Rule of thumb: Write kebab-case in your files; rely on relaxed binding to map env vars (UPPER_SNAKE_CASE) onto the same properties — no extra code needed.

Spring Boot supports both application.yml and application.properties. YAML is more readable for nested/hierarchical config; .properties is flatter and avoids YAML's indentation pitfalls.

# application.yml — hierarchy is visual:
app:
  mail:
    from: [email protected]
    retries: 3
    bcc-list:
      - [email protected]
      - [email protected]
# application.properties — equivalent, flat:
[email protected]
app.mail.retries=3
app.mail.bcc-list[0][email protected]
app.mail.bcc-list[1][email protected]

YAML shines for lists and deeply nested structures; its risks are indentation errors and surprising type coercion (e.g. unquoted on/off parse as booleans). Don't mix the two formats for the same profile — pick one per file to avoid confusing precedence.

Rule of thumb: Prefer YAML for structured, nested config; use .properties when you want flat, copy-paste-safe lines or are wary of YAML indentation bugs.

Add @Validated to a @ConfigurationProperties class and annotate fields with JSR-380 (Bean Validation) constraints. Invalid configuration then fails fast at startup rather than blowing up at runtime.

@ConfigurationProperties(prefix = "app.mail")
@Validated
public class MailProperties {
    @NotBlank                       // must be present and non-empty
    private String from;

    @Min(1) @Max(10)                // bounded retries
    private int retries = 3;

    @Email                          // must be a valid address
    private String replyTo;
    // getters/setters
}

If app.mail.from is missing, the context fails to start with a clear binding/validation error naming the offending property — far better than discovering it when the first email send fails in production. Requires a Bean Validation provider (Hibernate Validator, pulled in by spring-boot-starter-validation).

Rule of thumb: Validate config at startup with @Validated + constraints — a broken setting should crash the app immediately, not silently misbehave hours later.

Instead of mutable fields with setters, you can bind to constructor parameters, making the configuration object immutable. Java records are the natural fit.

// Immutable config via a record — no setters, all-args constructor:
@ConfigurationProperties(prefix = "app.mail")
public record MailProperties(
    String from,
    @DefaultValue("3") int retries,    // default when property is absent
    List<String> bccList) { }

// Enable it (records aren't component-scanned as beans by default):
@Configuration
@EnableConfigurationProperties(MailProperties.class)
public class ConfigPropsConfig { }

With constructor binding there are no setters to mutate state after startup, so the config is thread-safe and clearly final. Use @DefaultValue for absent properties. Since Spring Boot 3, a @ConfigurationProperties class with a single non-default constructor uses constructor binding automatically — no @ConstructorBinding needed on the type.

Rule of thumb: Prefer record-based constructor binding for config you never mutate at runtime — it's immutable, thread-safe, and reads as a clear contract.

@Profile accepts boolean expressions, and spring.profiles.group lets one profile name expand into several, so you can model environments compactly.

// Expression: active only when prod is on AND legacy is off
@Bean
@Profile("prod & !legacy")
DataSource pooledDataSource() { /* ... */ }

// '|' (or), '&' (and), '!' (not) are all supported:
@Profile("dev | test")
DataSource h2DataSource() { /* ... */ }
# Grouping: activating 'production' turns on three profiles at once
spring.profiles.group.production=prod,metrics,tracing
spring.profiles.active=production

Profile groups keep activation simple in deployment — set one profile, get a coherent set. Expressions keep bean conditions readable instead of nesting multiple @Conditionals.

Rule of thumb: Use spring.profiles.group to bundle related profiles behind one name, and @Profile("a & !b") expressions instead of stacking conditions.

If no profile is explicitly activated, Spring Boot runs the default profile. As soon as you set spring.profiles.active, the default profile is no longer active.

// Runs ONLY when no other profile was activated:
@Bean
@Profile("default")
DataSource localH2() { /* convenient local fallback */ }
# With nothing set → 'default' is active, localH2 is created.
# With this set → 'default' is NOT active; you must provide a prod DataSource.
spring.profiles.active=prod

You can even change which profile is the fallback via spring.profiles.default. The default profile is handy for "works out of the box on a laptop" beans that should disappear the moment a real environment profile is chosen.

Rule of thumb: Put laptop-only convenience beans under @Profile("default") so they vanish automatically once any real profile (dev/prod) is activated.

Secrets should never be committed in application.properties. Inject them at runtime via environment variables, a secrets manager, or Spring Cloud Config — relaxed binding maps env vars onto your properties automatically.

# Reference an env var with a placeholder; no secret in the file:
spring.datasource.password=${DB_PASSWORD}
app.api.key=${PAYMENT_API_KEY}
# Provided by the platform (Docker secret, Kubernetes secret, CI vault):
export DB_PASSWORD='s3cr3t'
export PAYMENT_API_KEY='pk_live_...'
# Because SPRING_DATASOURCE_PASSWORD also maps via relaxed binding, the
# platform can inject it either way.

For richer needs use HashiCorp Vault (Spring Cloud Vault), AWS Secrets Manager, or Kubernetes Secrets mounted as env vars/files. Keep a committed application.properties free of any real secret — only placeholders and non-sensitive defaults.

Rule of thumb: Commit placeholders, inject secrets from the environment or a vault at runtime — a real credential should never appear in version control.

@EnableConfigurationProperties registers one or more @ConfigurationProperties classes as beans. You need it when the properties class isn't already picked up by component scanning.

// The properties class has no @Component:
@ConfigurationProperties(prefix = "app.mail")
public class MailProperties { /* fields + getters/setters */ }

// Register it explicitly so it becomes an injectable bean:
@Configuration
@EnableConfigurationProperties(MailProperties.class)
public class MailConfig { }

// Alternative: annotate the properties class itself and rely on scanning:
// @ConfigurationProperties(prefix = "app.mail")
// @Component   ← then no @EnableConfigurationProperties needed

A third option, @ConfigurationPropertiesScan on the main class, scans a package for all @ConfigurationProperties types at once. The explicit @EnableConfigurationProperties approach is cleanest for library code and keeps the properties class free of Spring stereotype annotations.

Rule of thumb: Keep @ConfigurationProperties classes annotation-light and register them with @EnableConfigurationProperties (or @ConfigurationPropertiesScan).

The ${...} placeholder syntax references another property, with an optional default after a colon used when the property is unset. Placeholders can also nest and reference environment variables.

# Default value if the property is missing (the part after ':')
server.port=${PORT:8080}

# Compose one property from another:
app.name=orders-service
app.full-name=${app.name}-${spring.profiles.active:local}

# Nested placeholder: the key itself comes from another property
app.region=${REGION:us-east-1}
app.bucket=data-${app.region}
// Same syntax inside @Value:
@Value("${app.timeout-ms:5000}") long timeoutMs; // 5000 if unset

Without a default, an unresolved placeholder fails the context at startup — usually what you want for required values. Provide a default only when a sensible fallback exists.

Rule of thumb: Use ${KEY:fallback} to make optional config self-documenting; omit the fallback for required values so a missing one fails fast at startup.

spring.config.import (the Config Data API, Spring Boot 2.4+) declaratively pulls in additional configuration sources — extra files, directories, or external systems like Vault and Consul — from within application.properties itself.

# Import additional files (optional: prefix tolerates a missing source):
spring.config.import=optional:file:./config/local.properties,\
                     optional:classpath:shared-defaults.yml

# Import from an external system (with the relevant starter on the classpath):
spring.config.import=vault://secret/myapp,consul:

Imported sources are merged using the normal precedence rules. This replaced the older, more confusing spring.profiles-based document activation and bootstrap.yml for many cases. The optional: prefix prevents startup failure when a source is absent (e.g. a local override file that only exists on a developer machine).

Rule of thumb: Use spring.config.import to compose configuration from multiple files and external stores declaratively, and prefix with optional: for sources that may not exist everywhere.

More ways to practice

The self-quiz is live. Join our channel for updates, new content & tech tips.

Join our WhatsApp Channel