skip to content

Where do @DynamicPropertySource properties sit in Spring's property precedence, and what are the common gotchas that make them silently not apply?

level: middleimportance: should knowfreq 34%

answer

  1. high precedence — overrides properties files
  2. static or it won't wire
  3. supplier not eager getter
  4. container must be static + started
  5. wrong key = silent fallback

basics

~10 s

Their property source is added with high precedence, so it overrides application.properties and @TestPropertySource defaults. Common gotchas: forgetting static, evaluating the value eagerly, or the container not being started before the property is read.

solid answer

~40 s

@DynamicPropertySource contributes a dedicated property source that Spring inserts near the top of the Environment, so its values win over application.properties, application-test.properties, and typically over @TestPropertySource for the same key. Practical gotchas that make it silently misbehave: (1) the method isn't static — it's a configuration error and won't wire; (2) you invoke the getter eagerly (container.getJdbcUrl()) instead of passing a supplier (container::getJdbcUrl), risking a not-yet-started container exception or stale value; (3) the container is an instance field rather than static, so the static method can't reference it; (4) the container isn't started before property resolution (missing @Testcontainers/@Container or a static start block); (5) expecting it to read a Spring bean, which it can't. When debugging, remember its high precedence means it, not your properties file, is the effective value.

code

java · 15 lines
java
@Testcontainers
@SpringBootTest
class RedisCacheIT {

    @Container
    static final GenericContainer<?> REDIS =
            new GenericContainer<>("redis:7").withExposedPorts(6379);

    @DynamicPropertySource
    static void redisProps(DynamicPropertyRegistry registry) {
        // Correct keys + suppliers; these override application-test.properties
        registry.add("spring.data.redis.host", REDIS::getHost);
        registry.add("spring.data.redis.port", () -> REDIS.getMappedPort(6379));
    }
}

go deeper

for a junior

Should know it overrides the properties file for the same key.

for a middle

Should list the main silent-failure gotchas (static, supplier, wrong key).

for a senior

Should reason about precedence vs @TestPropertySource and debugging.

for a principal

Should connect precedence and gotchas to reliable, fast integration-test design.

## Precedence Spring's `Environment` layers **PropertySources** in a defined order; earlier sources override later ones for the same key. `@DynamicPropertySource` values are exposed through a **`DynamicValuesPropertySource`** that the test framework adds **with high precedence** — effectively at/near the top. Consequences: - They **override** `application.properties`, `application-test.properties`, and profile-specific files. - They generally **override `@TestPropertySource`** and inlined properties for the same key (dynamic values are meant to be the authoritative runtime value). - OS env vars / system properties set outside the JVM don't normally compete for these test-specific keys, but be mindful if you also export the same key. So when a value seems 'wrong', check whether a `@DynamicPropertySource` is quietly setting it — it wins over your file. ## Common gotchas (the silent failures) 1. **Not `static`.** A non-static `@DynamicPropertySource` method is invalid; Spring throws during processing. If you somehow don't see the failure, the properties simply aren't applied. Always `static`. 2. **Eager value vs supplier.** `registry.add("k", c.getJdbcUrl())` evaluates now; if the container isn't started you get `IllegalStateException` (mapped port) or a wrong value. Use `c::getJdbcUrl`. 3. **Instance container field.** The static method can only see **static** fields. A non-static container won't be reachable — make the container `static`. 4. **Container not started.** Without `@Testcontainers` + `@Container`, a `static { c.start(); }` block, or the singleton pattern, the supplier fires against a stopped container → exception at property-resolution time (often surfacing as a confusing bean-creation failure). 5. **Expecting bean access.** The method runs before context refresh; it cannot read beans. Use `DynamicPropertyRegistrar` (Spring 6.1+) for bean-derived values. 6. **Wrong property key.** Registering `spring.datasource.jdbc-url` instead of `spring.datasource.url` means auto-config ignores it and falls back to defaults — no error, just an embedded/failed datasource. 7. **Type mismatch.** The supplier returns `Object`; a numeric port passed where a String is expected usually works via conversion, but returning the wrong shape (e.g. a URL where a host is expected) causes connection failures. ## Debugging tips - Log the effective Environment or enable `--debug` to see which property source supplied a key. - Verify the container is up (`c.isRunning()`) if resolution throws. - Confirm the method is `static` and uses method references. ## When to use vs alternatives - Runtime container value, arbitrary key → `@DynamicPropertySource`. - Supported container, no key wrangling → `@ServiceConnection`. - Value from a bean → `DynamicPropertyRegistrar`.

  • If a value from application-test.properties and @DynamicPropertySource both set spring.datasource.url, which wins?
    The @DynamicPropertySource value, because its property source is registered with higher precedence than properties files.
  • You registered spring.datasource.jdbc-url and the datasource still uses an embedded default. Why?
    That's the wrong key; Spring expects spring.datasource.url, so the misnamed property is ignored and auto-config falls back silently.

saying these in an interview costs you the question

  • Saying application.properties overrides @DynamicPropertySource.
  • Assuming a wrong property key raises an error rather than silently falling back.
  • Thinking the method reads beans or a non-static container field.

context