skip to content

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%

answer

  1. static = per-class, once
  2. instance = per-method, fresh each test
  3. mirrors @BeforeAll vs @BeforeEach
  4. reset data, not the container
  5. missing static = slow suite

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.

solid answer

~50 s

The 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
java
@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

for a junior

Know static = shared once, instance = fresh each test.

for a middle

Explain the @BeforeAll/@BeforeEach mapping and why static is the default for DBs.

for a senior

Discuss data-reset strategies, sharing across classes, and context caching interplay.

for a principal

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.

context