What is `@DynamicPropertySource` for, and how does it interact with `@TestPropertySource` / `@SpringBootTest(properties=...)`?
answer
- runtime values, not compile-time constants
- static method + DynamicPropertyRegistry
- registry.add(name, Supplier) — lazy
- highest precedence, above @TestPropertySource
- Testcontainers; @ServiceConnection alternative
basics
~10 s@DynamicPropertySource registers properties whose values are only known at runtime — like a Testcontainers JDBC URL — via a static method taking a DynamicPropertyRegistry. Its values have the highest precedence, above @TestPropertySource and @SpringBootTest(properties).
solid answer
~40 s`@DynamicPropertySource` solves the problem that annotation attributes must be compile-time constants: it lets you register properties computed at runtime. You write a `static void` method annotated with it that receives a `DynamicPropertyRegistry`, then call `registry.add("key", supplier)` — the value is a `Supplier` evaluated lazily when the property is first read, after your containers/resources have started. It's the canonical way to wire Testcontainers (`add("spring.datasource.url", container::getJdbcUrl)`). On precedence, its property source is registered **higher than `@TestPropertySource` and `@SpringBootTest(properties)`**, so a dynamic value overrides both an inlined property and a file. Because the method is static and runs during context setup, it can read fields initialized by `@Container`/`@BeforeAll`. Spring Boot 3.1+ also offers `@ServiceConnection` as a higher-level alternative for common containers, reducing the need to hand-map each property.
code
java · 22 lines@SpringBootTest(properties = "spring.jpa.hibernate.ddl-auto=validate")
@Testcontainers
class OrderRepositoryIT {
@Container
static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:16");
@DynamicPropertySource
static void datasource(DynamicPropertyRegistry registry) {
// Suppliers -> evaluated lazily, after the container is up.
registry.add("spring.datasource.url", db::getJdbcUrl); // overrides any static value
registry.add("spring.datasource.username", db::getUsername);
registry.add("spring.datasource.password", db::getPassword);
}
@Autowired OrderRepository repository;
@Test
void persistsAgainstRealPostgres() {
assertThat(repository.count()).isZero();
}
}go deeper
Know it exists for runtime values like a container URL.
Explain the static method + DynamicPropertyRegistry.add(name, Supplier) pattern.
Articulate its top precedence and correct Testcontainers wiring with lazy suppliers.
Compare with @ServiceConnection, reason about cache impact and precedence conflicts across all sources.
## The problem it solves Annotation attributes (`properties`, `args`, `@TestPropertySource`) require **compile-time constants**. But integration tests often depend on values known only at runtime: a Testcontainers database exposes a random port and a generated JDBC URL, a mock server binds an ephemeral port. You cannot bake those into an annotation. ## The mechanism `@DynamicPropertySource` marks a **`static`** method that accepts a `DynamicPropertyRegistry`: ```java @Container static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:16"); @DynamicPropertySource static void props(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", db::getJdbcUrl); registry.add("spring.datasource.username", db::getUsername); registry.add("spring.datasource.password", db::getPassword); } ``` Key points: - The method **must be static** (it runs before the context/instance exists) and must accept exactly a `DynamicPropertyRegistry`. - `registry.add(name, Supplier)` stores a **`Supplier`**, not a value. The supplier is invoked **lazily** each time the property is resolved, so the container is guaranteed started by then. (Since Spring 6.2 there's also `registry.getFirst(...)` style helpers, but `add` is standard.) ## Precedence — the interplay Spring adds the dynamic properties as a property source with **higher precedence than everything else in a test**, per the reference docs: higher than `@TestPropertySource`, the OS environment, Java system properties, and application-declared `@PropertySource`. So the effective order (highest first): 1. **`@DynamicPropertySource`** 2. `@TestPropertySource` inlined `properties` / `@SpringBootTest(properties)` 3. `@TestPropertySource(locations)` 4. command-line `args` 5. system properties / OS env 6. `application.properties` Consequence: if you set `spring.datasource.url` in both `@SpringBootTest(properties)` and `@DynamicPropertySource`, the **dynamic** one wins. This is usually what you want (the real container URL), but it's a gotcha if you intended the static override to take effect. ## Interaction details - You can **combine** them: use `@TestPropertySource`/`properties` for static knobs (pool size, flags) and `@DynamicPropertySource` only for the runtime-derived keys. - Dynamic registration is **per test class** (the static method belongs to the class), and like other property config it participates in the **context cache key** — different dynamic setups fork contexts. - **Ordering among multiple `@DynamicPropertySource` methods** in a class hierarchy: all run; later-registered keys can overwrite earlier ones for the same name. ## Alternatives Spring Boot 3.1 introduced **`@ServiceConnection`** on a `@Container` field, which auto-configures the relevant `spring.datasource.*`/`spring.data.*` properties for supported containers — often eliminating the boilerplate `@DynamicPropertySource` method entirely. Prefer it when the container type is supported; fall back to `@DynamicPropertySource` for custom/unsupported keys. ## Common mistakes - Making the method **non-static** → Spring won't invoke it as expected / fails. - Passing a **value instead of a Supplier** (`registry.add("url", db.getJdbcUrl())`) → evaluates too early, before the container is started, or captures a stale value. - Expecting `@SpringBootTest(properties)` to override a dynamic key — it won't; dynamic wins.
- Why does `registry.add` take a `Supplier` rather than a plain value?Lazy evaluation. The supplier runs when the property is first read — by then the container has started and exposes its real URL/port. A plain value would be captured too early, before startup.
- You set `spring.datasource.url` in `@SpringBootTest(properties)` AND in `@DynamicPropertySource`. Which applies?The `@DynamicPropertySource` value. Dynamic properties are registered with higher precedence than inlined test properties, so they override them.
- What newer Spring Boot feature can replace much of this boilerplate?`@ServiceConnection` (Boot 3.1+) on the `@Container` field auto-maps standard connection properties for supported containers, removing the need for a manual `@DynamicPropertySource` method.
saying these in an interview costs you the question
- Making the `@DynamicPropertySource` method non-static
- Passing an eager value instead of a `Supplier`
- Claiming `@TestPropertySource` overrides `@DynamicPropertySource`
- Believing you can inline a container URL in `properties={}`