skip to content

How do you connect a Spring Boot application context to a Testcontainers-managed database, and why can't you just hardcode the URL?

level: seniorimportance: should knowfreq 55%

answer

  1. random host port → URL unknown till start
  2. @DynamicPropertySource = static, register suppliers
  3. @ServiceConnection = Boot 3.1+ auto-wire
  4. jdbc:tc: URL scheme alternative
  5. stable URL keeps context cache warm

basics

~20 s

Testcontainers maps the container's port to a random host port, so the JDBC URL isn't known until the container starts. You feed it into Spring at runtime with @DynamicPropertySource (or @ServiceConnection in Boot 3.1+), not a hardcoded property.

solid answer

~40 s

By default Testcontainers publishes the container's internal port to a random free host port to avoid collisions, so the real JDBC URL — host, port, generated credentials — only exists after the container starts. You therefore can't put it in application.properties. The classic wiring is a static @DynamicPropertySource method that registers suppliers (registry.add("spring.datasource.url", postgres::getJdbcUrl)); these are lazily resolved after the container is up but before the context refreshes. In Spring Boot 3.1+, @ServiceConnection on the container field is cleaner: Boot detects the container type and auto-configures the matching connection details (datasource, Redis, Kafka, etc.) with no property plumbing. Both require the container to be static so it starts before the context. This also keeps the Spring context cache effective, since a stable set of resolved properties lets multiple test classes reuse one context.

code

java · 22 lines
java
// Boot 3.1+ preferred: no property plumbing
@Testcontainers
@SpringBootTest
class OrderServiceIT {

    @Container
    @ServiceConnection                       // Boot auto-configures the datasource
    static PostgreSQLContainer<?> pg =
            new PostgreSQLContainer<>("postgres:16");

    @Autowired OrderService orderService;

    @Test void worksAgainstRealDb() { /* ... */ }
}

// Classic equivalent (any Spring version):
@DynamicPropertySource
static void props(DynamicPropertyRegistry r) {
    r.add("spring.datasource.url", pg::getJdbcUrl);   // supplier, not value!
    r.add("spring.datasource.username", pg::getUsername);
    r.add("spring.datasource.password", pg::getPassword);
}

go deeper

for a junior

Know the URL comes from the container at runtime, not a hardcoded property.

for a middle

Wire it with a static @DynamicPropertySource using method-reference suppliers.

for a senior

Prefer @ServiceConnection in Boot 3.1+; explain the lazy-supplier ordering and why static is required.

for a principal

Tie stable connection details to Spring context-cache reuse and suite runtime; weigh jdbc:tc scheme vs explicit containers.

**The core problem: dynamic ports.** When Testcontainers starts a container it maps the container's internal port (Postgres listens on 5432 inside the container) to a **random available port on the host** — e.g. 49173 — so that many containers, CI jobs, and developer machines never collide on a fixed port. The library also generates or exposes credentials. Consequently the actual connection string (`jdbc:postgresql://localhost:49173/test`) is **unknown at compile time and unknown until `start()` returns**. A hardcoded `spring.datasource.url` in `application.properties` would point at the wrong (or no) port and the context would fail to connect. **Solution 1 — `@DynamicPropertySource` (Spring Framework 5.2.5+):** a `static` method annotated with `@DynamicPropertySource` receives a `DynamicPropertyRegistry`. You register **suppliers**, not values: `registry.add("spring.datasource.url", postgres::getJdbcUrl)`. The method itself runs early, but the suppliers are invoked **lazily** when the environment resolves those properties — which happens **after** the static container has started and **before** the `ApplicationContext` refreshes. This ordering is exactly why the container must be `static`: static `@Container` fields start in the `@BeforeAll` phase, ahead of context creation. `PostgreSQLContainer` conveniently exposes `getJdbcUrl()`, `getUsername()`, `getPassword()`. **Solution 2 — `@ServiceConnection` (Spring Boot 3.1+):** annotate the container field directly: `@Container @ServiceConnection static PostgreSQLContainer<?> pg = ...`. Boot recognizes the container type and contributes a `ConnectionDetails` bean (`JdbcConnectionDetails`, `RedisConnectionDetails`, `KafkaConnectionDetails`, etc.) that overrides auto-configuration — **no `@DynamicPropertySource` needed at all**. It's the modern, preferred approach for supported technologies. For a bare `GenericContainer`, you can still use `@ServiceConnection(name = "...")` in some cases, but unsupported images fall back to `@DynamicPropertySource`. **Solution 3 — the JDBC URL scheme:** Testcontainers also offers a special URL like `jdbc:tc:postgresql:16:///databasename` where the `tc:` prefix makes the driver start a container on the fly. This needs no `@Container`/`@Testcontainers` at all and can live directly in properties — handy for quick setups, less flexible for wait strategies and reuse. **Why this matters for speed — context caching:** Spring's `TestContext` framework caches `ApplicationContext`s keyed by configuration. If every class produced a *different* datasource URL the cache would thrash, rebuilding a context per class. With a shared static/singleton container the resolved URL is stable across classes, so they reuse one cached context — a large suite-wide win. `@ServiceConnection` and consistent `@DynamicPropertySource` both preserve this. **Gotchas:** - `@DynamicPropertySource` methods **must be static**; a non-static one is ignored/errors. - Registering a *value* instead of a *supplier* (`registry.add("url", postgres.getJdbcUrl())`) evaluates too early — before the container is guaranteed started — and can capture a stale/empty value; always pass a method reference/lambda. - Mixing `@ServiceConnection` and a manual `@DynamicPropertySource` for the same datasource can conflict; pick one. - The container must be reachable before refresh, hence `static`; an instance container won't be up when the context builds.

  • Why must you register a supplier (method reference) rather than the resolved URL value in @DynamicPropertySource?
    The method runs early; a plain value would be read before the container is guaranteed started, capturing a stale/empty URL. A supplier defers resolution until Spring actually reads the property, by which time the container is up.
  • What advantage does @ServiceConnection give beyond less code?
    It contributes a typed ConnectionDetails bean that overrides auto-configuration for the specific technology (JDBC, Redis, Kafka, MongoDB...), so it also works when there's no simple property equivalent, and it stays correct if Boot changes property names.

saying these in an interview costs you the question

  • Hardcoding the JDBC URL/port in application.properties for a container.
  • Making the @DynamicPropertySource method non-static.
  • Passing pg.getJdbcUrl() (a value) instead of pg::getJdbcUrl (a supplier).
  • Claiming the container's port is fixed at 5432 on the host.

context