How would you use @BeforeAll and @AfterAll to manage an expensive shared resource like a test container or embedded server, and what should you watch out for?
answer
- Start once (@BeforeAll), stop once (@AfterAll), reset per test (@BeforeEach)
- Shared resource -> isolation risk -> per-test cleanup
- Guard @AfterAll against a null/half-built resource
- Extensions / Testcontainers singleton for cross-class sharing
- Parallel tests need a thread-safe shared resource
basics
~20 sStart the expensive resource (e.g. a database container) once in @BeforeAll and stop it once in @AfterAll, so all tests in the class reuse it instead of paying the startup cost each time. Make sure each test cleans up its own data so tests stay independent.
solid answer
~50 sThe pattern is: acquire a slow-to-create, reusable resource exactly once in @BeforeAll (start an embedded DB, a Testcontainers container, an HTTP server, a thread pool), expose it via a static field, and release it in @AfterAll so nothing leaks. The win is speed — startup happens once, not per test. The risk is shared mutable state: because the resource is reused, tests can pollute each other. So I keep the shared resource itself stable and push per-test cleanup into @BeforeEach/@AfterEach — e.g. truncate tables or reset the schema between tests — so each test sees a clean slate. I also make @AfterAll robust (close in a finally-style way, guard nulls if @BeforeAll failed) so a failed setup doesn't cascade. With parallel execution I'd ensure the resource is thread-safe or scope it appropriately. Testcontainers' Singleton-container pattern is the JVM-shutdown variant of this idea.
code
java · 26 linesimport org.junit.jupiter.api.*;
class UserRepositoryTest {
static EmbeddedDatabase db; // shared, started once
@BeforeAll
static void startDb() {
db = EmbeddedDatabase.start(); // expensive: do it once
db.applySchema();
}
@BeforeEach
void cleanState() {
db.truncateAll(); // per-test reset keeps tests isolated
}
@AfterAll
static void stopDb() {
if (db != null) { // guard: @BeforeAll may have failed
db.close();
}
}
@Test void insertsUser() { /* uses db, clean table guaranteed */ }
@Test void findsByEmail() { /* independent of insertsUser */ }
}go deeper
Can put a single resource start in @BeforeAll and stop in @AfterAll for one test class.
Adds per-test reset in @BeforeEach to keep the shared resource from coupling tests, and closes resources reliably.
Designs the full pattern: shared start, per-test cleanup, robust teardown, and knows when to reach for extensions or PER_CLASS.
Owns the strategy across the suite: cross-class sharing via extensions/singleton containers, parallel-execution safety, and the speed-vs-isolation trade-off as a testing standard.
## What 'expensive shared resource' means Some things tests depend on are **slow or heavy to create**: a real database started in a container, an **embedded** (in-process) database, a web server, a message broker, a big in-memory index, or a thread pool. Creating one can take hundreds of milliseconds to seconds. If you create it in `@BeforeEach`, you pay that cost **once per test**; for a class with 20 tests that's 20x the cost. ## The core pattern 1. **Acquire once** in `@BeforeAll`: start the resource and store the handle in a **`static` field** (required under the default lifecycle — no instance exists yet). 2. **Use** the resource in each `@Test` (and in `@BeforeEach` for per-test prep). 3. **Release once** in `@AfterAll`: close/stop/shutdown the resource so the process doesn't leak connections, ports, threads, or temp files. This trades **startup cost** (now paid once) against **isolation** (the resource is shared). ## The isolation problem and its fix Because the resource is reused, **state created by one test is visible to the next**. A test that inserts rows can make a later test fail if that test assumed an empty table — and worse, the failure can be **order-dependent** and flaky. The standard fix: - Keep the **expensive resource** itself in `@BeforeAll` (stays up the whole class). - Put **per-test reset** in `@BeforeEach`/`@AfterEach`: truncate tables, roll back a transaction, clear a cache, recreate the schema. Now each test gets a clean slate over a shared, already-started resource. ## Making teardown robust `@AfterAll` should **not throw on a half-built resource**. If `@BeforeAll` failed partway, the field may be `null`; guard it. Close in an exception-safe way so one close failure doesn't hide others. JUnit runs `@AfterAll` even if tests failed, but if `@BeforeAll` itself throws, the tests are skipped — so don't rely on `@AfterAll` to clean up something `@BeforeAll` never created. ## Better alternatives for some cases - **`@TestInstance(PER_CLASS)`** lets these be non-static instance methods and use instance fields — cleaner for some styles, at the cost of no per-test instance reset. - **JUnit 5 extensions** (`BeforeAllCallback`/`AfterAllCallback`, or `@RegisterExtension`) move resource lifecycle into a **reusable extension** so many test classes share it without copy-pasting `@BeforeAll` blocks. This is the idiomatic way Testcontainers integrates. - **Testcontainers Singleton-container pattern**: start the container in a static initializer and **never stop it explicitly** — let it die on JVM shutdown (Ryuk/`.withReuse`) so it's shared across *all* test classes, not just one. ## Parallel execution caveat If JUnit runs tests in **parallel**, a shared mutable resource must be **thread-safe** or the tests must not interfere (separate schemas/keys per test). `@BeforeAll`/`@AfterAll` still run once, but concurrent tests hit the resource simultaneously. ## Summary Start once in `@BeforeAll`, stop once in `@AfterAll`, reset per test in `@BeforeEach`; guard teardown; consider an extension or singleton pattern when many classes need the same resource.
- If @BeforeAll throws, does @AfterAll still run?If @BeforeAll throws, the tests in that class are not executed. @AfterAll behavior depends on how far setup got; the safe rule is to make @AfterAll null-safe and not assume @BeforeAll fully completed. Multiple @BeforeAll methods that did succeed have their matching cleanup considerations — guard everything.
- When would you prefer a JUnit 5 extension over @BeforeAll?When several test classes need the same resource lifecycle. An extension (BeforeAllCallback/AfterAllCallback or @RegisterExtension) encapsulates start/stop once and is reused across classes, avoiding copy-pasted @BeforeAll blocks — exactly how Testcontainers integrates.
saying these in an interview costs you the question
- Putting expensive startup in @BeforeEach 'to be safe'
- Sharing mutable state via @BeforeAll without any per-test reset
- Assuming @AfterAll always has a fully built resource to close
- Ignoring thread-safety under parallel execution