skip to content

@Testcontainers & @Container

Testcontainers starts a real Postgres, Kafka or Redis in Docker for the test, with the extension managing lifecycle and static versus instance fields deciding per-class or per-method containers. Interviewers ask about test time, which is exactly what that choice controls.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

What is Testcontainers and why use the @Testcontainers / @Container annotations in a JUnit 5 test?

level: juniorimportance: must knowfreq 70%

answer

  1. real Docker deps, not H2
  2. @Testcontainers = register extension
  3. @Container = managed lifecycle field
  4. PostgreSQLContainer vs GenericContainer
  5. needs a Docker daemon

basics

~20 s

Testcontainers starts a real service (like PostgreSQL) in a throwaway Docker container for tests. @Testcontainers on the class turns on lifecycle management, and @Container marks a container field so it is started before tests and stopped after.

solid answer

~40 s

Testcontainers is a Java library that spins up real dependencies (databases, message brokers, etc.) inside disposable Docker containers so integration tests run against the genuine software instead of mocks or in-memory fakes like H2. In JUnit 5 you put @Testcontainers on the test class to register its extension, which scans for fields annotated with @Container and manages their start/stop lifecycle automatically. A container is typically a PostgreSQLContainer or a GenericContainer wrapping any image. Because it is the real engine, you catch dialect-specific SQL, real constraints, and driver behavior that fakes hide. The trade-off is you need a Docker daemon available and startup adds seconds per container, so field scope (static vs instance) matters for speed.

code

java · 19 lines
java
@Testcontainers
class OrderRepositoryIT {

    @Container
    static PostgreSQLContainer<?> postgres =
            new PostgreSQLContainer<>("postgres:16-alpine");

    @DynamicPropertySource
    static void datasourceProps(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }

    @Test
    void savesAndLoadsAgainstRealPostgres() {
        // ... talks to a real Postgres in a throwaway container
    }
}

go deeper

for a junior

Know it runs real services in Docker for tests, and the role of the two annotations.

for a middle

Know the module split (junit-jupiter vs postgresql) and how to wire the URL into Spring via @DynamicPropertySource.

for a senior

Contrast fidelity vs H2, and mention @ServiceConnection for Boot 3.1+ auto-wiring.

for a principal

Weigh CI cost/image caching, reuse, and Testcontainers Cloud vs local Docker as an infra decision.

**Testcontainers** is an open-source Java library that programmatically starts and stops **Docker containers** during automated tests. A Docker container is an isolated, disposable process running a packaged image (for example the official `postgres` image). Instead of testing your data-access code against an in-memory database like **H2** (which behaves differently from production Postgres), Testcontainers boots the *real* Postgres in a container, lets your test talk to it over a mapped TCP port, then throws it away. **The two annotations (JUnit 5):** - `@Testcontainers` is a class-level annotation that registers the `TestcontainersExtension` (a JUnit 5 extension). Without it, nothing manages your containers automatically. It is provided by the artifact `org.testcontainers:junit-jupiter`. - `@Container` marks a field whose value is a container object. The extension finds these fields and drives their lifecycle: it calls `start()` before the appropriate tests and `stop()` (which also removes the container) afterward. You never call `start()`/`stop()` yourself when using these annotations. **Container types:** - `GenericContainer<>` wraps *any* Docker image — you pass an image name and configure exposed ports, environment variables, and wait strategies. - `PostgreSQLContainer<>`, `MySQLContainer<>`, `KafkaContainer`, etc. are **specialized modules** that pre-configure the right ports, credentials, and readiness checks, and expose helpers like `getJdbcUrl()`, `getUsername()`, `getPassword()`. **Minimal flow:** declare `@Container PostgreSQLContainer<?> pg = new PostgreSQLContainer<>("postgres:16")` → the extension starts it → your test reads `pg.getJdbcUrl()` to connect → the extension stops it. **Why not H2/mocks?** H2 in Postgres-compatibility mode still diverges on JSON, arrays, `ON CONFLICT`, sequences, and casting; mocks test nothing about SQL at all. Testcontainers gives production fidelity. **Prerequisites & gotchas:** a working Docker daemon (or a Testcontainers-compatible runtime like Colima/Podman/Testcontainers Cloud) must be reachable, or every test errors at startup. Container startup costs seconds, so how you scope fields (covered in other questions) directly affects suite runtime. Images are pulled on first use, so CI needs network or a warmed cache. **Spring angle:** Testcontainers is framework-agnostic, but with Spring Boot you wire the container's JDBC URL into the context — classically via `@DynamicPropertySource`, or in Boot 3.1+ via `@ServiceConnection` on the container field, which auto-configures the datasource with zero property plumbing.

  • Which dependency provides the @Testcontainers and @Container annotations?
    The org.testcontainers:junit-jupiter module (the JUnit 5 integration). The core org.testcontainers:testcontainers artifact provides GenericContainer, and per-tech modules like org.testcontainers:postgresql add PostgreSQLContainer.
  • Why prefer a real Postgres container over H2 with PostgreSQL compatibility mode?
    H2's compatibility mode is an approximation; it diverges on JSON/array types, ON CONFLICT upserts, sequences, casting, and dialect-specific SQL. A container runs the actual engine, so tests catch production-only failures instead of passing falsely.

saying these in an interview costs you the question

  • Thinking Testcontainers is an in-memory database (it runs real Docker containers).
  • Believing @Testcontainers alone starts containers without @Container fields.
  • Assuming no Docker daemon is needed.

context

open as a page

What is the difference between a static @Container field and an instance @Container field, and how does it change the container lifecycle?

level: middleimportance: must knowfreq 65%

basics

~20 s

A static @Container field starts once and is shared by all test methods in the class (per-class). An instance (non-static) @Container field is started and stopped fresh for every test method (per-method), giving isolation but running slower.

open as a page

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%

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.

open as a page

When wrapping an arbitrary image in GenericContainer, how does Testcontainers know the service is actually ready, and what goes wrong if the wait strategy is wrong?

level: seniorimportance: should knowfreq 40%

basics

~20 s

start() blocks until a wait strategy says the container is ready. By default it waits for the mapped ports to accept connections. For custom images you often set a better one — like waiting for a log line or an HTTP 200 — otherwise tests hit a service that's up but not yet accepting real requests.

open as a page

Your integration suite spins up Postgres, Kafka, and Redis containers and has grown slow and occasionally flaky. As a principal engineer, how do you architect Testcontainers usage for speed and reliability?

level: principalimportance: should knowfreq 30%

basics

~20 s

Share containers instead of recreating them: use static or singleton containers reused across all test classes, wire stable connection details so Spring's context cache is reused, reset data between tests instead of restarting containers, set correct wait strategies to kill flakiness, and consider reuse/Testcontainers Cloud in CI.

open as a page