What is the difference between a static @Container field and an instance @Container field, and how does it change the container lifecycle?
answer
- static = per-class, once
- instance = per-method, fresh each test
- mirrors @BeforeAll vs @BeforeEach
- reset data, not the container
- missing static = slow suite
basics
~20 sA 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.
solid answer
~50 sThe lifecycle scope follows the field modifier. A static @Container is tied to the class lifecycle: the extension starts it once before any test runs (via BeforeAll semantics) and stops it once after all tests complete — every @Test in the class shares the same container instance. A non-static (instance) @Container is tied to the method lifecycle: it is started before each @Test and stopped after each one, so every method gets a pristine container. Static is the common default because starting a database container costs seconds and you rarely want to pay that per method; you keep tests independent by cleaning data (transactions, @Sql, TRUNCATE) instead. Instance scope is for the rarer case where the container itself must be fresh — for example when a test mutates container-level state you cannot easily reset. This mirrors JUnit 5's @BeforeAll (static) vs @BeforeEach (instance) timing.
code
java · 16 lines@Testcontainers
class ContainerScopeExamples {
// PER-CLASS: started once, shared by every @Test
@Container
static PostgreSQLContainer<?> shared =
new PostgreSQLContainer<>("postgres:16");
// PER-METHOD: restarted fresh before each @Test (rarely needed, slow)
@Container
GenericContainer<?> perTest =
new GenericContainer<>("redis:7").withExposedPorts(6379);
@Test void a() { /* 'shared' already running; 'perTest' just started */ }
@Test void b() { /* same 'shared'; a NEW 'perTest' instance */ }
}go deeper
Know static = shared once, instance = fresh each test.
Explain the @BeforeAll/@BeforeEach mapping and why static is the default for DBs.
Discuss data-reset strategies, sharing across classes, and context caching interplay.
Reason about parallel execution hazards, singleton/reuse patterns, and suite-wide runtime budgets.
The `@Container` field's **modifier decides its lifecycle**, and this is the single most important operational detail of Testcontainers with JUnit 5. **Static `@Container` → per-class (shared):** - The `TestcontainersExtension` starts a `static` container **once**, before the first test in the class (aligned with JUnit 5's `@BeforeAll` phase), and stops it **once** after the last test (aligned with `@AfterAll`). - All `@Test` methods share the *same running container instance* and therefore the same data unless you reset it. - This is the recommended default for expensive backing stores (databases, Kafka) because container startup dominates runtime — paying it once per class instead of once per method can turn minutes into seconds. **Instance (non-static) `@Container` → per-method (fresh):** - The extension starts the container **before every `@Test`** (`@BeforeEach` phase) and stops it **after every `@Test`** (`@AfterEach` phase). - Each test gets a brand-new container with zero prior state — maximal isolation, maximal cost. **Why static is usually right:** you get isolation *between tests* far more cheaply by resetting *data* rather than recreating the *container*. Common reset strategies: run each test in a rolled-back transaction (`@Transactional` on the test), re-seed with `@Sql`, `TRUNCATE`/`DELETE` in `@BeforeEach`/`@AfterEach`, or use Testcontainers' database-specific features. The container stays up; only the rows change. **Sharing across many classes:** static scope still restarts the container once *per test class*. To share **one** container across an entire suite, move it out of any single class — e.g. a base class holding the static container that all IT classes extend, or the **singleton container pattern** (a static field in a holder class started manually in a static initializer and never stopped — Ryuk, the Testcontainers reaper sidecar, cleans it up when the JVM exits). Container **reuse** (`.withReuse(true)` plus `testcontainers.reuse.enable=true` in `~/.testcontainers.properties`) can even keep it alive across separate JVM runs for local dev. **Gotchas:** - Forgetting `static` when you meant to share silently makes tests slow (a container per method) — a frequent cause of "why is my suite so slow?" - With static containers + `@DynamicPropertySource` (which must also be `static`), the URL is resolved once; mutable per-test config won't re-apply. - Parallel test execution complicates a shared static container — concurrent tests hit the same database and can interfere; either serialize or partition data by test. - Spring's context cache means a static container plus a stable property set lets many test classes reuse the *same* application context, compounding the speedup. **Mnemonic:** static ↔ `@BeforeAll` ↔ once per class; instance ↔ `@BeforeEach` ↔ once per method.
- If a static container is shared, how do you keep tests independent?Reset data, not the container: wrap each test in a rolled-back @Transactional, re-seed via @Sql, or TRUNCATE/DELETE in @BeforeEach. The container stays up; only its state is cleaned between tests.
- How would you share one container across many test classes, not just methods?Put the static container in a shared base class the ITs extend, or use the singleton-container pattern (static field started in a static initializer, never stopped — Ryuk reaps it at JVM exit). Optionally enable reuse for cross-run persistence locally.
saying these in an interview costs you the question
- Claiming a static @Container restarts for every test method.
- Thinking instance fields are the normal/recommended choice.
- Believing each test needs a fresh container for isolation (data reset is enough).
- Forgetting @DynamicPropertySource must be static too.