skip to content

What pitfalls and precedence rules govern ${} interpolation across the pom, resources, and the command line — and how would you debug an unresolved or wrong value?

level: seniorimportance: should knowfreq 30%

answer

  1. -D > <properties> > built-ins
  2. filters feed filtering only
  3. no filtering => token stays literal
  4. Spring ${} collision -> @...@
  5. help:evaluate / effective-pom to debug

basics

~20 s

Interpolation replaces ${name} from sources with a precedence order (roughly -D system props > pom properties > built-ins). Pitfalls: unfiltered resources keep tokens literally, ${} clashes with framework placeholders, and unknown tokens stay as-is. Debug with mvn help:evaluate and effective-pom.

solid answer

~40 s

${} interpolation pulls values from multiple sources with a precedence order: command-line system properties (-D) and JVM system properties generally win, then pom <properties>, then built-in families (project.*, settings.*, env.*), with filter files feeding only resource filtering. Key pitfalls: (1) a token in a resource file is only replaced if that resource has filtering enabled — otherwise it is copied literally; (2) frameworks like Spring use ${} too, so filtering can wrongly consume them — fix via delimiters or by not filtering; (3) unknown properties are left as the literal ${name} rather than blanked or failing; (4) recursive/self-referential definitions can fail to resolve. To debug, I use mvn help:evaluate -Dexpression=... to see resolved values, mvn help:effective-pom to see the merged model, and check the filtered output under target/classes.

code

bash · 3 lines
bash
mvn help:evaluate -Dexpression=project.version -q -DforceStdout
mvn help:effective-pom -Doutput=effective-pom.xml
mvn package && cat target/classes/application.properties

go deeper

for a junior

Knows ${} is replaced and that unresolved ones can remain literal.

for a middle

Knows filtering must be on and the basic source list.

for a senior

Reasons about precedence, the Spring collision, encoding, and debugs with help goals.

for a principal

Sets org conventions for delimiters/encoding and fails-fast strategies so silent unresolved tokens never reach prod.

## How interpolation works When Maven builds the **effective model** and when the resources plugin filters files, it scans for `${name}` and substitutes a resolved value. If `name` cannot be resolved, the literal `${name}` is left in place (Maven does not error or blank it by default). ## Precedence (which source wins) For the same name, the effective order is roughly: 1. Command-line / JVM **system properties** (`-Dname=value`). 2. Pom `<properties>` (with child overriding inherited parent). 3. Built-in families: `project.*`, `settings.*`, `env.*`. 4. For resource filtering only: external `<filters>` files (later filter overrides earlier). So `mvn -Dspring.version=6.2 package` beats a `<spring.version>6.1</spring.version>` in the pom. ## Pitfall 1 — token only replaced if filtering is on A `${project.version}` inside `src/main/resources/app.properties` stays literal unless that `<resource>` has `<filtering>true</filtering>`. The most common 'why is my placeholder not replaced' cause. ## Pitfall 2 — framework `${}` collision Spring, Logback, etc. use `${}` for *their* runtime placeholders. With filtering on, Maven resolves them at build time — breaking the framework. Mitigate by changing Maven's delimiter (e.g. use `@prop@` in resources via the resources-plugin `<delimiters>` / `useDefaultDelimiters=false`), escaping, or isolating those files in a non-filtered directory. ## Pitfall 3 — unknown tokens stay literal A typo like `${projetc.version}` does not fail; it ships the literal text. Catch it by inspecting `target/classes`. ## Pitfall 4 — encoding Filtering rewrites files; set `project.build.sourceEncoding` so non-ASCII content isn't mangled. ## Debugging toolkit ```bash # resolve any expression against the effective model mvn help:evaluate -Dexpression=project.version -q -DforceStdout # dump the fully-merged pom (after inheritance + interpolation) mvn help:effective-pom # inspect the actually-filtered output ls target/classes && cat target/classes/application.properties ``` `help:evaluate` is the fastest way to confirm what a property resolves to; `help:effective-pom` shows how inheritance and property overrides merged.

  • A ${...} in your properties file is shipping literally. First thing to check?
    Whether that resource directory has <filtering>true</filtering>; without it, tokens are copied verbatim.
  • How do you stop Maven from resolving Spring's ${} placeholders during filtering?
    Change Maven's filtering delimiter (e.g. to @prop@) via the resources plugin, escape the token, or keep that file in a non-filtered directory.
  • What happens to an unknown/typo'd property token during filtering?
    It is left as the literal ${typo} text — the build does not fail or blank it by default.

saying these in an interview costs you the question

  • Saying an unresolved ${name} blanks out or fails the build.
  • Forgetting that filtering must be enabled for resource tokens to resolve.
  • Claiming pom <properties> always override -D system properties.

context