skip to content

@Nested

@Nested inner classes group tests by scenario and let each group layer its own setup on top of the outer class's. Interviewers see it as a sign you organize tests around behavior rather than one flat class per class.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

4

Why must a @Nested class be non-static, and how does that affect access to the outer test instance's state?

level: middleimportance: must knowfreq 50%

answer

  1. Non-static inner class ⇒ holds Outer.this reference
  2. Static nested ⇒ no enclosing instance, breaks sharing
  3. Default lifecycle PER_METHOD: fresh outer per test method
  4. Whole enclosing chain instantiated per nested test
  5. PER_CLASS reuses instance, can leak mutable state

basics

~20 s

A @Nested class must be non-static so it holds a reference to an instance of the outer test class. That lets nested tests read and reuse the outer class's fields and setup. A static nested class would be independent and couldn't share that instance state.

solid answer

~50 s

JUnit's default test instance lifecycle is PER_METHOD: a fresh outer instance is created for each test method. A @Nested class is a non-static inner class, so each of its instances is bound to an instance of the enclosing class and can access the outer instance's fields and helpers. JUnit instantiates the full enclosing chain for every nested test, so outer @BeforeEach setup and shared fields are available to nested tests — that's the whole point of nesting: arrange common state in the parent, refine it in the child. If the class were static, it would be an independent nested class with no implicit outer instance, breaking the shared-context model JUnit depends on; JUnit therefore requires @Nested classes to be non-static. Note the outer instance is fresh per test method (unless you opt into @TestInstance(PER_CLASS)), so nested tests don't accidentally leak mutable state between methods.

code

java · 17 lines
java
class OrderTest {
    Order order;

    @BeforeEach
    void base() { order = new Order(); }

    @Nested
    class WhenPaid {
        @BeforeEach
        void pay() { order.markPaid(); }  // shares outer 'order'

        @Test
        void isComplete() {
            assertTrue(order.isComplete());
        }
    }
}

go deeper

for a junior

Knows the nested class must be non-static and that this lets it use the outer class's fields.

for a middle

Explains the Outer.this reference, why static breaks sharing, and that PER_METHOD gives a fresh outer instance per test method.

for a senior

Reasons about lifecycle (PER_METHOD vs PER_CLASS) consequences for shared mutable state and designs nested setup to avoid coupling between methods.

for a principal

Guides the team on lifecycle conventions and isolation guarantees so large nested suites stay deterministic and free of cross-test state leakage.

## The Java mechanism behind the rule In Java, a class declared inside another class is **nested**. There are two kinds: - A **static nested class** (declared `static`) is essentially a top-level class that happens to live inside another for namespacing. It has *no* link to any instance of the enclosing class. - A **non-static inner class** (no `static` keyword) is tied to an **instance** of the enclosing class. Every inner-class object carries a hidden reference to an *outer-class object* (you can write it explicitly as `Outer.this`). Through that reference, inner-class code can read the outer object's **fields** and call its methods. `@Nested` requires the **non-static** form precisely because JUnit wants the nested tests to *share* the enclosing test's context — its fields and its setup. ## Test instance lifecycle JUnit 5 creates **test instances** to run methods on. By default the lifecycle is **`PER_METHOD`**: for *every* `@Test` method, JUnit builds a brand-new instance of the test class. When that test lives in a `@Nested` class, JUnit builds the **entire enclosing chain** — an instance of the outer class *and* an instance of the nested class linked to it — for that one test method. Because the nested instance is linked to a fresh outer instance, any field on the outer class, and any `@BeforeEach` it ran, is visible to the nested test: ```java class OrderTest { Order order; // outer field @BeforeEach void base() { order = new Order(); } // outer setup runs first @Nested class WhenPaid { @BeforeEach void pay() { order.markPaid(); } // reads the SAME outer 'order' @Test void isComplete() { assertTrue(order.isComplete()); } } } ``` Here the nested `WhenPaid.pay()` mutates the `order` field that the outer `base()` created — they share the same outer instance for that test. ## Why static breaks it If you wrote `static class WhenPaid`, the class would have **no enclosing instance**. It couldn't reference the non-static `order` field at all (it wouldn't compile), and JUnit would not run the outer instance setup as part of its lifecycle. The shared-context model collapses. So JUnit's contract is: **@Nested test classes must be non-static inner classes.** (Top-level test classes are the opposite — they're effectively standalone — but nested *grouping* classes are inner.) ## PER_METHOD vs PER_CLASS With the default `PER_METHOD`, state does **not** leak between test methods because each gets a fresh outer+nested instance pair. If you annotate a class with **`@TestInstance(TestInstance.Lifecycle.PER_CLASS)`**, JUnit reuses one instance across all that class's methods (which also enables non-static `@BeforeAll`). That choice changes whether mutable fields persist across methods — relevant when reasoning about shared state in nested groups, and a source of accidental coupling if you mutate shared fields and rely on `PER_CLASS`. ## Summary Non-static is mandatory so the nested test inherits an *instance* of the enclosing test, giving it access to outer fields and setup. That shared, per-method-fresh context is exactly what makes `@Nested` useful for layering arrangement from general (parent) to specific (child).

  • What happens if you mark a @Nested class static?
    It loses the enclosing instance link, can't access non-static outer fields (compile error if it tries), and JUnit won't treat it as a context-sharing nested group. JUnit requires non-static.
  • Does outer-instance state leak between nested test methods by default?
    No. The default PER_METHOD lifecycle builds a fresh outer+nested instance pair per test method, so mutable fields are reset. Only PER_CLASS would reuse an instance and risk leakage.

saying these in an interview costs you the question

  • Claiming nested tests share one outer instance across all methods by default (they don't — PER_METHOD is fresh per method).
  • Saying a static class works the same as @Nested.
  • Forgetting that a static nested class can't access non-static outer fields at all.

context

open as a page

How do lifecycle methods like @BeforeEach and @AfterEach stack and execute across nesting levels in JUnit 5?

level: seniorimportance: must knowfreq 48%

basics

~20 s

Outer @BeforeEach methods run before inner ones, going from the outermost class inward, then the test runs, then @AfterEach methods run in reverse order — innermost first, outermost last. Each level layers its own setup and teardown around the test.

open as a page

What is JUnit 5's @Nested annotation, and why would you use it to organize your tests?

level: juniorimportance: should knowfreq 45%

basics

~20 s

@Nested marks an inner test class inside another test class. It groups related tests together so you can express scenarios like 'when the cart is empty' as their own block with their own setup, making tests easier to read and organize.

open as a page

When does @Nested improve a test suite versus harm it, and what design trade-offs guide how deeply you nest?

level: principalimportance: should knowfreq 30%

basics

~20 s

Use @Nested when tests fall into clear scenarios that share setup, so the report reads like sentences and duplication drops. Avoid nesting so deeply that setup becomes hard to follow or scenarios get artificial. Keep nesting shallow and meaningful.

open as a page