skip to content

When should you use an EnvironmentPostProcessor versus the ConfigData API or @ConfigurationProperties, and how do multiple EPPs order against Boot's own ConfigDataEnvironmentPostProcessor?

level: principalimportance: nice to knowfreq 22%

answer

  1. @ConfigurationProperties = consume; ConfigData = new location; EPP = imperative mutate
  2. ConfigDataLocationResolver + ConfigDataLoader for spring.config.import
  3. ConfigDataEnvironmentPostProcessor order = HIGHEST_PRECEDENCE + 10
  4. lower order value = earlier; late EPP sees application.properties
  5. make EPP idempotent, stable source names

basics

~20 s

Use an EPP for broad, imperative shaping of the whole Environment early (compute/decrypt/inject property sources). Use the ConfigData API to add a new config location type (like a custom spring.config.import). Use @ConfigurationProperties only to read config into beans. Order EPPs with Ordered/@Order; Boot's ConfigDataEnvironmentPostProcessor runs at very high precedence.

solid answer

~40 s

Three different tools. @ConfigurationProperties/@Value just *consume* properties into beans — no mechanism to add sources. The ConfigData API (ConfigDataLocationResolver + ConfigDataLoader, Boot 2.4+) is the modern way to teach Boot a new config *location* so users can write spring.config.import=myscheme:...; it's declarative, profile- and import-aware, and cache-friendly. An EnvironmentPostProcessor is the imperative escape hatch: run arbitrary code over the whole ConfigurableEnvironment to compute defaults, decrypt secrets, or mutate/reorder any source — useful when what you need isn't tied to a single location or predates ConfigData. Ordering: Boot loads config files via its own ConfigDataEnvironmentPostProcessor at order HIGHEST_PRECEDENCE + 10, so implement Ordered to run after it (higher order value) if you must see application.properties/yaml, or before it to influence which files/profiles it loads.

code

java · 31 lines
java
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import java.util.Map;

// Runs AFTER Boot's ConfigDataEnvironmentPostProcessor (order HIGHEST_PRECEDENCE + 10)
// because LOWEST_PRECEDENCE is a larger value => later. So application.properties
// is already loaded and we can override it.
public class LateOverrideEnvironmentPostProcessor
        implements EnvironmentPostProcessor, Ordered {

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment env,
                                       SpringApplication application) {
        // e.g. decrypt or override values now that file config is present
        env.getPropertySources().addFirst(
                new MapPropertySource("decryptedSecrets",
                        Map.of("db.password", decrypt(env.getProperty("db.password")))));
    }

    private String decrypt(String cipher) { /* ... */ return cipher; }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE; // run late
    }
}

go deeper

for a junior

Just knows @ConfigurationProperties reads config; EPP is for changing it early.

for a middle

Can distinguish consuming vs producing config and knows EPPs mutate sources.

for a senior

Chooses ConfigData API vs EPP correctly and handles ordering relative to file loading.

for a principal

Designs library config integration, reasons about ConfigDataEnvironmentPostProcessor ordering, profile timing, idempotency, and composability trade-offs.

## Three tools, three jobs ### 1. @ConfigurationProperties / @Value — *consume* config These bind already-resolved properties into beans. They add nothing to the Environment and run at **bean time** (context refresh). Reach for them 95% of the time — when you just want to read config. They are irrelevant if your goal is to *produce* or *transform* config. ### 2. ConfigData API — add a new config *location type* (Boot 2.4+) The modern config system is built on two SPIs: - **`ConfigDataLocationResolver`** — recognizes a location string (e.g. `spring.config.import=vault://secret/app`) and resolves it to `ConfigDataResource`s. - **`ConfigDataLoader`** — loads a resolved resource into `ConfigData` (property sources). Register them in `spring.factories`. This is **declarative and location-oriented**: users opt in with `spring.config.import`, it's fully **profile-aware** (imported docs can have profile-specific sections), participates in Boot's ordering and caching, and composes cleanly. Choose this when your config has a natural **location/URI** and you want first-class integration (the way `configtree:`, `vault:`, `consul:` work). ### 3. EnvironmentPostProcessor — imperative, whole-Environment shaping The **escape hatch**: arbitrary code with the entire `ConfigurableEnvironment` in hand. Best when: - The transformation isn't tied to one location — e.g. **decrypt any `{cipher}` value across all sources**, derive properties from other properties, or **reorder/remove** sources. - You need to run **very early**, even to influence which config files load. - You're targeting older Boot or a case ConfigData doesn't model. Downside: imperative, less composable, no built-in profile/import semantics — you own correctness. ## Decision guide - Just reading config → `@ConfigurationProperties`. - Adding a resolvable *source/location* users import → **ConfigData API**. - Broad imperative mutation / decryption / computed defaults / reordering → **EnvironmentPostProcessor**. ## Ordering with Boot's own EPP Boot loads `application.properties`/`application.yml` (and processes `spring.config.import`) via **`ConfigDataEnvironmentPostProcessor`**, itself an `EnvironmentPostProcessor` registered at order **`Ordered.HIGHEST_PRECEDENCE + 10`** — i.e. it runs **very early**. Since lower order value = earlier: - Give your EPP a **higher order value** (e.g. `LOWEST_PRECEDENCE`) to run **after** config files are loaded when you need to *read or override* application.properties/yaml. - Give it a value **lower** than `HIGHEST_PRECEDENCE + 10` to run **before** config-data loading — e.g. to set a default profile or inject a source that the file-loading logic should see. Control this by implementing `org.springframework.core.Ordered` (or `@Order`). If you `addBefore("applicationConfig: ...", ...)` you must run *after* the source exists, or the named source won't be present and the call throws. ## Gotchas at this level - **Profiles**: activating profiles from an EPP is fragile if it runs before/after config-data processing; prefer setting `spring.profiles.active` as a property early, and understand that ConfigData resolves profile-specific documents itself. - **Idempotency & duplicates**: EPPs may run more than once across event types in some flows; make mutation idempotent and use stable source names. - **Testability**: EPPs are plain objects — unit-test by constructing one and passing a `StandardEnvironment`; no Spring context needed. - **Library authors**: prefer ConfigData for anything users will `import`; reserve EPPs for cross-cutting transforms so you don't fight Boot's ordering.

  • You want users to write spring.config.import=acme:core to pull config from your system. EPP or ConfigData API?
    ConfigData API — implement a ConfigDataLocationResolver to recognize the acme: prefix and a ConfigDataLoader to load it. That's the location-oriented, profile- and import-aware mechanism; an EPP would be a clumsier imperative substitute.
  • Your EPP needs to read application.yml values but they're null. Why, and how do you fix it?
    Your EPP runs before ConfigDataEnvironmentPostProcessor (order HIGHEST_PRECEDENCE + 10) has loaded the config files. Implement Ordered with a higher order value (e.g. LOWEST_PRECEDENCE) so it runs after config-data loading.

saying these in an interview costs you the question

  • Recommending an EnvironmentPostProcessor to add a custom spring.config.import location instead of the ConfigData API.
  • Assuming an EPP always sees application.properties regardless of ordering.
  • Thinking @ConfigurationProperties can add property sources to the Environment.

context