Why must a @DynamicPropertySource method be static, and why do you register a supplier (method reference) rather than a resolved value?
answer
- no instance yet → static
- before context refresh
- add(name, supplier) stores the lambda
- lazy resolve = container already started
- container::getJdbcUrl not getJdbcUrl()
basics
~20 sIt must be static because it runs before any test instance or the context exists. You register a supplier so Spring reads the value lazily — later, once the container has actually started — instead of capturing it too early.
solid answer
~40 sThe method is invoked by Spring's test-context framework very early: before the ApplicationContext is refreshed and before a test class instance is constructed. Since no instance exists yet, the method has to be static. You pass a Supplier (typically a method reference like container::getJdbcUrl) instead of calling the getter directly because Spring resolves the value lazily, at the moment the property is actually needed during bean creation. This lazy resolution guarantees the Testcontainers container is already running and its mapped port/URL are final. If you eagerly evaluated container.getJdbcUrl() while registering, you might read state before the container started — or, with @Testcontainers lifecycle, hit a not-yet-running container — producing a wrong or failing URL. The registry stores the supplier and calls it on demand.
code
java · 14 lines@Testcontainers
@SpringBootTest
class KafkaConsumerIT {
@Container
static final KafkaContainer KAFKA =
new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.6.0"));
@DynamicPropertySource
static void kafkaProps(DynamicPropertyRegistry registry) {
// method reference => lazy: getBootstrapServers() called after start
registry.add("spring.kafka.bootstrap-servers", KAFKA::getBootstrapServers);
}
}go deeper
May just know it must be static; that's acceptable at junior.
Should articulate both the static requirement and lazy-supplier timing.
Should tie lazy resolution to container-startup ordering and the mapped-port exception.
Should discuss supplier re-invocation and side-effect-free requirements.
## Why `static` Spring's test framework processes `@DynamicPropertySource` methods through `TestContextManager`/`DynamicPropertySourceBeanInitializer` during the **preparation of the ApplicationContext**. At that point: - The **context isn't built yet** (that's the whole point — properties must exist *before* refresh). - No **test class instance** has been created (JUnit constructs the test instance around individual test execution; for `PER_CLASS` it's still tied to lifecycle, but the property registration happens at context-loading time). Because there is no `this`, the method must be `static`. A non-static `@DynamicPropertySource` method is a configuration error — Spring will reject it (it throws during processing), so it won't silently work. ## Why a `Supplier` (lazy) not a value (eager) `DynamicPropertyRegistry.add(String name, Supplier<Object> valueSupplier)` stores the **supplier**, not the resolved value. Spring exposes these via a `DynamicValuesPropertySource` that **invokes the supplier each time the property is resolved** (lazily). Compare: ```java // GOOD — lazy: getJdbcUrl() called later, when container is up registry.add("spring.datasource.url", POSTGRES::getJdbcUrl); // RISKY — eager: getJdbcUrl() evaluated now, at registration time registry.add("spring.datasource.url", POSTGRES.getJdbcUrl()); ``` With **eager** evaluation, `getJdbcUrl()` runs while the `@DynamicPropertySource` method is being processed. Depending on container startup ordering, the container may not yet be started, so: - `getMappedPort(...)` throws `IllegalStateException: Mapped port can only be obtained after the container is started`. - Or you capture a stale/incorrect value. The **supplier** form defers the call until the property is actually read during bean instantiation, by which time the static container field is guaranteed started (either via `@Container` + `@Testcontainers`, a `static { POSTGRES.start(); }` initializer, or singleton-container pattern). ## Startup ordering to remember 1. Static container field is declared/started (static initializer or `@Testcontainers` `@Container`). 2. `@DynamicPropertySource` method registers **suppliers**. 3. Context refresh begins; beans (e.g. `DataSource`) resolve properties → suppliers fire → live values returned. ## Gotchas - Using a **non-static container** with a **static** property method: the method can't see an instance field. Container must also be static. - Autoboxing/typing: the supplier returns `Object`; ensure the type is what the property expects (usually String). - The supplier is invoked possibly **more than once** — keep it cheap and side-effect free (getters are fine).
- What happens if you write registry.add("...", container.getMappedPort(5432)) instead of a supplier?It evaluates eagerly at registration time; if the container isn't started yet, Testcontainers throws IllegalStateException about the mapped port, or you capture a wrong value.
- Can the container field be an instance field?No — because the @DynamicPropertySource method is static, it can only reference static fields, so the container must be static too.
saying these in an interview costs you the question
- Saying the supplier is evaluated immediately at registration.
- Suggesting the method can be an instance method if you use @TestInstance(PER_CLASS).
- Claiming static is just a style preference.