skip to content

Lifecycle in @Nested Classes

How outer-class hooks wrap inner-class tests and why @BeforeAll needs PER_CLASS inside @Nested. Asked to check you can reason about the full callback chain.

on this pageshow

questions

4

In JUnit 5, a test method lives in a class annotated @Nested inside an outer test class, and both classes declare @BeforeEach and @AfterEach methods. In what order do those four methods run around the test?

level: middleimportance: must knowfreq 55%

answer

  1. outside-in setup, inside-out teardown
  2. nested try/finally shape
  3. same rule as superclass/subclass hooks
  4. outer @BeforeAll covers nested tests too
  5. two instances: outer + inner per test

basics

~10 s

Outer @BeforeEach runs first, then the nested class's @BeforeEach, then the test, then the nested @AfterEach, then the outer @AfterEach. Setup runs outside-in, teardown inside-out — like nested try/finally blocks.

solid answer

~50 s

Setup runs **outside-in** and teardown **inside-out**: 1. outer class `@BeforeEach` 2. nested class `@BeforeEach` 3. the `@Test` method 4. nested class `@AfterEach` 5. outer class `@AfterEach` The rule generalizes: with several levels of nesting, every enclosing level's `@BeforeEach` runs before the level below it, and the `@AfterEach` methods unwind in reverse. It is the same ordering JUnit applies to inheritance — superclass setup first, subclass teardown first — and the same shape as nested `try { ... } finally { ... }` blocks. That is exactly why `@Nested` is useful for fixtures: the outer setup establishes the common precondition, and each nested class layers its own scenario on top without repeating it. `@AfterEach` methods run even when the test or an earlier `@AfterEach` fails, and Jupiter reports all failures rather than swallowing later ones. One more level: an outer `@BeforeAll` runs once before every test in the whole tree, nested classes included.

code

java · 15 lines
java
class CartTest {

    @BeforeEach void outerSetup()    { System.out.println("1 outer before"); }
    @AfterEach  void outerTeardown() { System.out.println("5 outer after"); }

    @Nested
    class WhenOneItemAdded {

        @BeforeEach void innerSetup()    { System.out.println("2 inner before"); }
        @AfterEach  void innerTeardown() { System.out.println("4 inner after"); }

        @Test
        void totalsTheItem() { System.out.println("3 test"); }
    }
}

go deeper

for a junior

Recite the five-step order and the phrase 'setup outside-in, teardown inside-out'.

for a middle

Add the try/finally model, the parallel with superclass/subclass hook ordering, and where the outer @BeforeAll sits.

for a senior

Bring in the practical consequence — the outer level owns the shared precondition, extensions registered outside apply to nested classes — plus teardown-on-failure semantics.

for a principal

Discuss when this layering earns its complexity versus explicit fixture factories, and what deep hierarchies cost a reader.

## What @Nested is In JUnit Jupiter, `@Nested` marks a **non-static inner class** inside a test class as a test class in its own right. It exists to express a hierarchy of scenarios — "when the cart is empty", "when the cart has one item" — where each level adds setup on top of the level above it. The reason the ordering question comes up in interviews is that this layering is only useful if you know exactly when each layer's hooks fire. ## The ordering rule For a test method inside a nested class, with `@BeforeEach`/`@AfterEach` declared at both levels: ``` outer @BeforeEach nested @BeforeEach @Test nested @AfterEach outer @AfterEach ``` So **setup is outside-in, teardown is inside-out**. Three levels of nesting simply extend the ladder: level 1 setup, level 2 setup, level 3 setup, test, level 3 teardown, level 2 teardown, level 1 teardown. The mental model that never fails is nested `try/finally`: each enclosing class wraps the one inside it, so whatever the outer level set up is still valid while the inner level runs, and the outer level cleans up last — after everything that depended on it has finished. The same principle governs inheritance: a superclass `@BeforeEach` runs before the subclass's, and the subclass's `@AfterEach` runs before the superclass's. `@Nested` is the composition-flavored version of the same idea. ## Where the class-level hooks fit `@BeforeAll` in the **outer** class runs once, before any test in the outer class *or in any nested class* — the nested classes are part of the outer class's test tree, so their tests are covered by it. `@AfterAll` in the outer class correspondingly runs after the last test anywhere in that tree. Class-level hooks in the nested class are the constrained case: an inner class historically cannot declare `static` members, so a nested `@BeforeAll` either requires `@TestInstance(Lifecycle.PER_CLASS)` on the nested class or a sufficiently modern Java language level and JUnit version. That is a separate discussion; for `@BeforeEach`/`@AfterEach` there is no such restriction at any level. ## Instances, not just methods Under the default per-method lifecycle, running one test in a nested class requires **two** objects: an instance of the outer class and an instance of the nested class bound to it (that is what "non-static inner class" buys you — the inner instance holds a reference to its enclosing instance). Both are created fresh for each test method. The outer `@BeforeEach` runs on the outer instance, the nested one on the nested instance, and the nested test can read the outer instance's fields directly. This is why the ordering matters so concretely: the outer `@BeforeEach` is what puts the outer fields into a usable state before the inner setup — and before the test — touches them. ## Failure semantics - If the **outer `@BeforeEach` throws**, the nested `@BeforeEach` and the test do not run, and Jupiter unwinds by running the teardown callbacks that correspond to the setup that had already begun; teardown is not skipped merely because something failed. - If the **test throws**, all `@AfterEach` methods still run, inner before outer. - If **several `@AfterEach` methods throw**, Jupiter records all of them (as suppressed exceptions on the reported failure) rather than reporting only the first — a detail that saves real debugging time. ## Multiple hooks at the same level Within one class, the relative order of several `@BeforeEach` methods is **not guaranteed by declaration order**; Jupiter deliberately leaves it deterministic-but-unspecified so you do not build dependencies between them. If ordering between two setup steps matters, put them in one method, or split them across levels of the hierarchy where the order *is* defined. There is an `@Order`-style mechanism for test methods, but the right fix for hooks is usually to remove the dependency. ## Extensions interleave the same way Registered extension callbacks (`BeforeEachCallback`, `AfterEachCallback`) follow the same outside-in/inside-out wrapping relative to the hierarchy, and extensions registered on the outer class also apply to nested classes — they are inherited down the tree. So an extension declared once on the outer class covers every scenario class beneath it, which is a common reason to structure tests this way. ## How to answer in an interview Give the five-step order, then the one-line rule ("setup outside-in, teardown inside-out, like nested try/finally"), then one consequence you have actually relied on — usually "the outer level owns the shared precondition so each nested scenario does not repeat it". That trio is a complete answer in under thirty seconds.

  • Where does an outer class's @BeforeAll fit into that sequence?
    It runs once, before any test in the whole tree — including every test inside the nested classes — because nested classes are part of the enclosing class's test tree. The matching @AfterAll runs once after the last test anywhere beneath it. So the full sequence for the first nested test is: outer @BeforeAll, outer @BeforeEach, nested @BeforeEach, test, nested @AfterEach, outer @AfterEach.
  • Two @BeforeEach methods are declared in the same class and one depends on the other having run. Is that safe?
    No. JUnit Jupiter does not guarantee that multiple lifecycle methods declared in the same class run in declaration order; the order is deterministic but intentionally unspecified so that tests do not encode dependencies between hooks. If two setup steps are ordered, merge them into a single method, or move one to an enclosing class level where the outside-in ordering is guaranteed.
  • If the test method fails, do the @AfterEach methods still run?
    Yes. Teardown runs regardless of the test's outcome, inner class first and then outer, mirroring a finally block. If several @AfterEach methods themselves throw, Jupiter aggregates the failures rather than reporting only the first, so you see every teardown problem in the report.

Getting dressed and undressed: coat goes on last and comes off first, base layer goes on first and comes off last.

saying these in an interview costs you the question

  • Saying the nested @BeforeEach runs before the outer one
  • Expecting teardown to run in the same direction as setup instead of unwinding
  • Assuming an outer @BeforeAll does not apply to tests inside nested classes
  • Believing multiple @BeforeEach methods in one class are ordered by declaration order
  • Thinking @AfterEach is skipped when the test fails

context

open as a page

Why must a class annotated @Nested in JUnit 5 be a non-static inner class, and what does that imply for fields that the enclosing class's setup methods initialize?

level: middleimportance: should knowfreq 44%

basics

~20 s

Because a non-static inner class instance holds a reference to an enclosing instance. JUnit builds the outer instance, runs its setup on it, then builds the inner instance bound to it — so nested tests can read and use the outer object's fields directly. A static nested class has no enclosing instance and is not treated as @Nested.

open as a page

You add a @BeforeAll method to a class annotated @Nested in JUnit 5 and the run fails saying the method must be static — but making it static does not compile. What causes this, and what are your options for one-time setup scoped to that nested class?

level: seniorimportance: should knowfreq 38%

basics

~20 s

@BeforeAll must be static by default, but a @Nested class is a non-static inner class, which historically cannot declare static members — hence the deadlock. Fix it by annotating the nested class @TestInstance(PER_CLASS), which allows a non-static @BeforeAll; or move the setup to the outer class, or to @BeforeEach, or to an extension.

open as a page

When would you model a test suite as a JUnit 5 @Nested class hierarchy that layers setup across levels, versus flat test classes with explicit fixture-builder methods? What degrades as the nesting gets deeper?

level: principalimportance: nice to knowfreq 26%

basics

~20 s

Nest when the scenarios form a real tree and each level adds one precondition — the layered @BeforeEach then reads as the story. Go flat with builder methods when preconditions are independent or the tree is deep: beyond two or three levels a reader must reconstruct state from several files' worth of hooks to understand one assertion.

open as a page