For a test method declared inside a JUnit 5 @Nested class, in what order do the enclosing and nested lifecycle callbacks run, how many objects does the framework create, and what does it take to declare @BeforeAll inside that nested class?
answer
- outside-in setup, inside-out teardown
- two instances per nested test (outer + inner)
- PER_METHOD default; PER_CLASS inherited by nested
- @BeforeAll static → Java 16+ inner-class statics
- pre-16 workaround: @TestInstance(PER_CLASS)
basics
~20 s@BeforeEach runs outside-in (outer then nested), @AfterEach inside-out. With the default per-method lifecycle each test gets a fresh outer instance plus a fresh nested instance. @BeforeAll must be static, which needs Java 16+ or @TestInstance(PER_CLASS) on the nested class.
solid answer
~50 sOrdering is **outside-in for setup, inside-out for teardown**: outer `@BeforeAll` → nested `@BeforeAll` → outer `@BeforeEach` → nested `@BeforeEach` → test → nested `@AfterEach` → outer `@AfterEach` → nested `@AfterAll` → outer `@AfterAll`. Extension callbacks registered at each level interleave the same way. With the default `@TestInstance(PER_METHOD)`, every test method gets a **new outer instance and a new inner instance bound to it** — so nothing leaks between tests, and `@BeforeEach` at both levels re-runs each time. `@BeforeAll`/`@AfterAll` must be `static` under `PER_METHOD`. Java did not allow static members in inner classes before **Java 16**, so on older JDKs the only way to get a once-per-nested-class hook was to annotate the nested class `@TestInstance(TestInstance.Lifecycle.PER_CLASS)` and declare the method non-static. On Java 16+ a `static @BeforeAll` compiles inside a `@Nested` class and works directly. Note that `PER_CLASS` also means one shared nested instance for all of its tests — you then own the isolation.
code
java · 33 linesclass ApiClientTest {
private Client client;
@BeforeEach
void createClient() {
client = new Client("http://localhost");
}
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class WhenServerIsDown {
@BeforeAll
void startFaultyStub() {
// non-static is legal because this container is PER_CLASS
}
@BeforeEach
void pointAtStub() {
client.retarget("http://stub");
}
@Test
void failsFast() {
assertThrows(IOException.class, client::ping);
}
@AfterAll
void stopStub() {
}
}
}go deeper
Know that outer setup runs first and that each test gets fresh objects; you are not expected to recall the @BeforeAll/PER_CLASS detail.
State the full onion ordering, the two-instances-per-test fact, and that @BeforeAll needs static or PER_CLASS inside a nested class.
Add why the ordering exists (resource acquisition/release symmetry), how extension callbacks interleave, and the isolation consequences of switching a container to PER_CLASS.
Discuss lifecycle mode as a suite-wide policy: where shared expensive fixtures justify PER_CLASS, how that interacts with parallel execution, and how you keep isolation guarantees explicit for the team.
## The full callback order JUnit 5 treats each nesting level as a container that wraps the level below it. That gives a strict onion ordering for a test method inside a nested class: 1. outer class `@BeforeAll` 2. nested class `@BeforeAll` 3. outer class `@BeforeEach` 4. nested class `@BeforeEach` 5. the `@Test` method 6. nested class `@AfterEach` 7. outer class `@AfterEach` 8. nested class `@AfterAll` (after all of that container's tests) 9. outer class `@AfterAll` (after everything) The rule to remember is **setup outside-in, teardown inside-out** — the same order a stack of try/finally blocks would give you. Extension callbacks (`BeforeEachCallback`, `AfterEachCallback` and friends) obey the same nesting: an extension registered on the outer class wraps everything the nested class does, because extensions declared at an enclosing level are inherited by nested containers. Within one level, multiple `@BeforeEach` methods have no guaranteed order unless you add `@Order` with `@TestMethodOrder`-style ordering support (for lifecycle methods, `@Order` is honoured by Jupiter's default method orderer for `@BeforeEach`/`@AfterEach` in recent versions); relying on it is a smell — merge them instead. ## How many instances Jupiter's default test-instance lifecycle is `PER_METHOD`: one fresh test-class instance per test method. For a nested test that means **two** instances per test — the enclosing one first, then the inner one constructed against it. Run three tests inside a nested class and Jupiter constructs three outer instances and three inner instances. This is the isolation guarantee people rely on: a field mutated by one nested test is invisible to the next, and to any sibling nested class. It is also why instance fields, not statics, are the right place for fixture state. The lifecycle mode is **inherited**: annotate the outer class `@TestInstance(TestInstance.Lifecycle.PER_CLASS)` and its `@Nested` classes run in `PER_CLASS` too unless one of them overrides it. Under `PER_CLASS`, Jupiter creates a single instance per container and reuses it for all of that container's tests, so state does leak between tests unless you clean it in `@AfterEach`. The upside is that `@BeforeAll`/`@AfterAll` may then be non-static instance methods. ## @BeforeAll inside a nested class Under `PER_METHOD`, `@BeforeAll` and `@AfterAll` must be `static` — there is no single instance for them to belong to. That collides with a Java language rule: **before Java 16, an inner (non-static member) class could not declare static members at all**, so `static void setUpAll()` inside a `@Nested` class simply did not compile. The historical workaround, and still the portable one, is: ```java @Nested @TestInstance(TestInstance.Lifecycle.PER_CLASS) class WithSeededDatabase { @BeforeAll void seed() { /* non-static is legal under PER_CLASS */ } } ``` Java 16 relaxed the rule (inner classes may declare static members), so on a modern toolchain a plain `static @BeforeAll` inside a `@Nested` class compiles and Jupiter runs it once for that container. Both approaches are valid; the `PER_CLASS` one additionally changes instance sharing, which is a bigger semantic change than people expect — mention that tradeoff in an interview. ## Why the ordering matters in practice Layered fixtures only work because of the outside-in rule: the outer `@BeforeEach` builds the base object and the nested `@BeforeEach` moves it into the scenario state. If it ran the other way round, the nested setup would operate on a field that is still null. The inside-out teardown matters for resources: a nested class that opened a transaction closes it before the outer class closes the connection it borrowed from. Getting these backwards is a classic source of "connection already closed" noise at the end of a suite. ## Failure semantics If an outer `@BeforeEach` throws, the nested `@BeforeEach` and the test do not run, and the test is reported as failed; the `@AfterEach` methods of levels that already completed setup still run. A failure in `@BeforeAll` at any level fails every test in that container and below. Jupiter aggregates exceptions from multiple lifecycle methods rather than swallowing them, so you see all of the causes.
- What changes if you put @TestInstance(PER_CLASS) on the outer class instead of the nested one?The mode is inherited, so both the outer class and every @Nested class inside it switch to one instance per container unless a nested class overrides it. That means outer fields survive between tests and you become responsible for resetting them, typically in @AfterEach. It also lets @BeforeAll/@AfterAll be non-static at every level.
- If the outer @BeforeEach throws, does the nested @AfterEach still run?No. Jupiter runs @AfterEach only for levels whose setup was entered, so a failure in the outer @BeforeEach skips both the nested @BeforeEach and the test, and only the outer @AfterEach methods that correspond to completed setup run. The test is reported as failed with that exception, and any additional exceptions from teardown are attached as suppressed.
saying these in an interview costs you the question
- Saying nested @BeforeEach runs before the outer one
- Claiming the outer instance is created once and shared across all nested tests under the default lifecycle
- Thinking @BeforeAll inside a @Nested class works with no extra step on any JDK
- Believing @TestInstance(PER_CLASS) only affects @BeforeAll and does not change instance sharing
- Assuming @AfterEach runs outside-in like @BeforeEach