skip to content

The parent enables resource filtering with @...@ delimiters instead of the default ${...}. Why, and how would you inject the Maven build version into application.properties?

level: seniorimportance: should knowfreq 40%

answer

  1. Maven default delimiter = ${...}
  2. Collides with Spring ${...}
  3. Parent sets resource.delimiter=@
  4. useDefaultDelimiters=false
  5. @project.version@ = build-time

basics

~10 s

Spring's own config uses ${...} placeholders, which would collide with Maven's filtering. So the parent switches Maven's resource-filtering delimiter to @, letting you write @project.version@ in application.properties while Spring's ${...} stays untouched.

solid answer

~40 s

Resource filtering is a Maven feature where `maven-resources-plugin` substitutes build properties into resource files at copy time. By default Maven uses `${...}` as the placeholder delimiter — but Spring uses `${...}` extensively for property placeholders (`${DB_URL}`) and for `@Value("${...}")`. If Maven filtered those, it would try to resolve them at build time and blank out or mangle them. To avoid the clash, `spring-boot-starter-parent` sets the property `resource.delimiter` to `@` and configures `maven-resources-plugin` with `useDefaultDelimiters=false`, so **only** `@...@` tokens are filtered. Then in `application.properties` you write, e.g., `[email protected]@`, and Maven replaces it with the POM version at package time, while runtime `${...}` placeholders pass through untouched. Filtering is enabled for `src/main/resources` by the parent.

code

kotlin · 15 lines
kotlin
// src/main/resources/application.properties
//   [email protected]@   // Maven fills at build time
//   [email protected]@             // -> e.g. 1.4.0 after package
//   spring.datasource.url=${DB_URL}                // Spring resolves at runtime

// Read the build-time value at runtime:
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

@Component
class BuildInfo(
    @Value("\${info.app.version}") val version: String,
) {
    fun banner() = "Running version $version"
}

go deeper

for a junior

May not know filtering exists; enough to know Maven can substitute @project.version@.

for a middle

Explains build-time vs runtime and that @ avoids the ${} clash.

for a senior

Names resource.delimiter/useDefaultDelimiters and knows filtering is limited to configured resource dirs.

for a principal

Weighs binary-resource pitfalls, custom resource dirs, and Maven-vs-Gradle mechanism differences and migration impact.

## What 'resource filtering' means In a Maven build, files under `src/main/resources` are **copied** into `target/classes` by the `maven-resources-plugin`. 'Filtering' means: during that copy, the plugin performs **variable substitution**, replacing placeholder tokens with values of Maven properties (POM coordinates like `project.version`, `project.artifactId`, values from `<properties>`, settings, etc.). It happens at **build time**, before the jar is assembled. ## The delimiter collision problem Maven's default filtering delimiter is **`${...}`**. But Spring Boot's runtime configuration mechanism *also* uses `${...}`: - Property placeholders: `spring.datasource.url=${DB_URL}` resolved at **runtime** by Spring's `PropertySourcesPlaceholderConfigurer`. - Annotations: `@Value("${server.port}")`. If Maven filtered `application.properties` with the default `${...}` delimiter, at **build time** it would try to resolve `${DB_URL}` as a Maven property, fail to find it, and either leave a warning or blank it — breaking runtime resolution. ## The parent's fix `spring-boot-starter-parent` reconfigures filtering so the two mechanisms don't collide: - It defines a property `resource.delimiter` with value `@`. - It configures `maven-resources-plugin` with that delimiter and `useDefaultDelimiters=false`, meaning the default `${...}` is **disabled** and only `@...@` is treated as a filter token. - It enables filtering on the standard resources directory. Result: a clean separation — **`@...@` = Maven build-time**, **`${...}` = Spring runtime**. ## Usage `application.properties`: ```properties [email protected]@ [email protected]@ app.datasource.url=${DB_URL} ``` After `mvn package`, the packaged file reads e.g. `app.build.version=1.4.0` while `${DB_URL}` is still literal, ready for Spring to resolve at startup. A common pairing is exposing the value via `@Value("${app.build.version}")` or Actuator info. ## Gotchas - This behavior is **inherited from the parent**. Drop the parent (use the BOM import) and you lose the `@` delimiter config — filtering reverts to `${...}` and you must reconfigure `maven-resources-plugin` yourself, or your `@...@` tokens won't be replaced. - Filtering only applies to directories marked as filtered. The parent handles `src/main/resources`; if you add custom resource dirs you must set `<filtering>true</filtering>` yourself. - Binary resources (images, keystores) should **not** be filtered — filtering them can corrupt bytes. Keep them in a non-filtered `<resource>` block. - The Gradle plugin uses a different mechanism (`processResources` with `expand`/`ReplaceTokens`); this `@...@` convention is Maven-specific.

  • What would happen if Maven filtered application.properties with the default ${...} delimiter?
    It would try to resolve Spring's runtime placeholders like ${DB_URL} at build time, fail (they aren't Maven properties), and blank or mangle them, breaking runtime configuration.
  • You switched to the spring-boot-dependencies BOM import and your @project.version@ token stopped resolving. Why?
    The @ delimiter and filtering config come from the parent's maven-resources-plugin setup. Without the parent you must set resource.delimiter/useDefaultDelimiters and enable filtering yourself.

saying these in an interview costs you the question

  • Claiming Spring uses @...@ for runtime placeholders
  • Thinking filtering happens at runtime
  • Filtering binary resources like keystores

context