skip to content

In the default StandardEnvironment, what is the precedence order between JVM system properties, OS environment variables, and an @PropertySource file — and why?

level: middleimportance: must knowfreq 60%

answer

  1. -D system props > OS env > @PropertySource file
  2. precedence = position in the ordered list
  3. first @PropertySource → addLast (bottom)
  4. later @PropertySource → addBefore → overrides earlier
  5. addFirst to promote a file above env

basics

~10 s

JVM system properties (-D) win over OS environment variables, which win over @PropertySource files. Lookup walks the ordered list and takes the first match, and @PropertySource files are added at the bottom.

solid answer

~40 s

StandardEnvironment registers systemProperties (from System.getProperties(), i.e. -Dkey=value) ahead of systemEnvironment (from System.getenv(), OS variables), so a JVM system property overrides an env var of the same name. @PropertySource-loaded files are added below both, so system properties and env vars override file values — deliberate, so operators can override packaged defaults without repackaging. Among multiple @PropertySource declarations, the last one processed wins over earlier ones (Spring inserts each new source before the previously added @PropertySource source). This precedence is realized purely by list position in MutablePropertySources: getProperty iterates first→last and returns the first match. You can change it programmatically with addFirst/addBefore. In Spring Boot the full ordering is richer (command line, SPRING_APPLICATION_JSON, config data files, etc.), but the core rule — first source wins — is identical.

code

java · 12 lines
java
// Two files: later declaration wins between them, but both sit below env/system props.
@Configuration
@PropertySource("classpath:base.properties")   // processed first -> lower
@PropertySource("classpath:override.properties") // processed later -> higher (among files)
public class Config { }

// Force a file ABOVE OS env vars at runtime:
var ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().getPropertySources()
   .addFirst(new ResourcePropertySource("classpath:hard-override.properties"));
ctx.register(Config.class);
ctx.refresh();

go deeper

for a junior

State that some sources override others and lookup takes the first match; may not know exact default order.

for a middle

Give the exact default order (system props → env → files) and explain it's just list position; know later @PropertySource beats earlier.

for a senior

Explain addFirst/addBefore to reorder, the CompositePropertySource merge, and contrast with Boot's fuller ordering.

for a principal

Reason about override strategy for ops/12-factor config, when to inject sources via EnvironmentPostProcessor, and pitfalls of relying on implicit ordering.

**The one rule that governs everything:** `MutablePropertySources` is an ordered list and `getProperty` returns the value from the **first** source (front of the list) that contains the key. "Precedence" is nothing more than list position. **Default StandardEnvironment order (highest→lowest):** 1. `systemProperties` — a `PropertiesPropertySource` over `System.getProperties()`; these are JVM `-Dkey=value` flags. 2. `systemEnvironment` — a `SystemEnvironmentPropertySource` over `System.getenv()`; these are OS environment variables. Because `systemProperties` is added **first**, `-Ddb.url=...` beats a `DB_URL` env var of the same logical name. **Where @PropertySource lands.** During config-class parsing, `ConfigurationClassParser.addPropertySource` runs. If it is the *first* @PropertySource it calls `propertySources.addLast(...)` — i.e. the **bottom** of the list, below system props and env vars. So by default file values are the **lowest** precedence and are overridden by both env and system properties. This is intentional: you ship sane defaults in the jar and let ops override via env/`-D` without rebuilding. **Multiple @PropertySource declarations.** For the *second* and later ones, Spring calls `addBefore(previousPropertySourceName, newSource)`. Effect: the **most recently processed** @PropertySource sits **above** earlier ones, so a later declaration overrides an earlier declaration — but all of them remain below system/env sources. If two @PropertySource annotations share the same `name`, Spring merges them into a `CompositePropertySource`. **Same-named source re-add.** Adding a source with a name that already exists doesn't duplicate; the composite/merge logic applies. **Changing precedence deliberately.** Because it's just a list, you can promote a file above env: `env.getPropertySources().addFirst(new ResourcePropertySource("classpath:override.properties"))`. Use `addFirst`, `addLast`, `addBefore(name, ps)`, `addAfter(name, ps)`, `replace(name, ps)`, `remove(name)`. **Spring Boot note.** Boot builds a much longer ordered list (highest→lowest, roughly): devtools, `@TestPropertySource`, command-line args, `SPRING_APPLICATION_JSON`, servlet params, JNDI, Java system properties, OS env vars, profile-specific and plain `application.properties`/`yml` (config data), `@PropertySource`, then default properties. The mechanism is identical — first match wins — only the list is longer, and Boot's app config files are loaded via `ConfigData`, not @PropertySource. **Common gotchas.** (1) Expecting a @PropertySource file to override an env var — it won't by default. (2) Expecting the *first* @PropertySource declaration to win — it's the *last* processed that wins among files. (3) Assuming `-D` and env of the same name are equal — system properties win.

  • You set DB_URL both as a -D system property and as an OS env var. Which does env.getProperty("DB_URL") return?
    The system property value, because systemProperties is ordered before systemEnvironment in StandardEnvironment and the first match wins.
  • How would you make a properties file override environment variables?
    Add it to the front of the list: environment.getPropertySources().addFirst(new ResourcePropertySource(...)), typically in an ApplicationContextInitializer / EnvironmentPostProcessor so it's registered before use. Plain @PropertySource can't do this — it's added at the bottom.

saying these in an interview costs you the question

  • Saying @PropertySource files override environment variables by default
  • Saying the first @PropertySource declaration wins over later ones
  • Treating -D system properties and OS env vars as equal precedence
  • Believing precedence is fixed and can't be changed programmatically

context