Close

JUnit 5 - Nested Class Lifecycle Annotations

[Last Updated: Dec 6, 2025]

Lifecycle annotations in nested classes behave differently than in top-level classes. While @BeforeEach and @AfterEach execute in a hierarchical manner, @BeforeAll and @AfterAll have specific requirements when used in nested classes.

Using @BeforeAll and @AfterAll in inner classes

Nested test classes can have @BeforeAll and @AfterAll methods (static) in modern JUnit 5 (version 5.5+).
Originally static @BeforeAll/@AfterAll methods were not allowed because inner classes couldn't have static methods before Java 16.
JUnit 5.5 introduced @TestInstance(Lifecycle.PER_CLASS) to enable non-static @BeforeAll/@AfterAll methods in inner classes.
Since Java 16 allows static members in inner classes, nested test classes can now use static @BeforeAll/@AfterAll methods directly.

Example

package com.logicbig.example;

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class NestedClassLifecycleAnnotationsTest {
    private static StringBuilder lifecycleLog = new StringBuilder();

    @BeforeAll
    static void beforeAllOuter() {
        lifecycleLog.append("Outer BeforeAll | ");
        System.out.println("Outer BeforeAll executed");
    }

    @AfterAll
    static void afterAllOuter() {
        lifecycleLog.append("Outer AfterAll | ");
        System.out.println("Outer AfterAll executed");
        System.out.println("Final Lifecycle Log: " + lifecycleLog.toString());
    }

    @BeforeEach
    void beforeEachOuter() {
        lifecycleLog.append("Outer BeforeEach | ");
        System.out.println("Outer BeforeEach executed");
    }

    @AfterEach
    void afterEachOuter() {
        lifecycleLog.append("Outer AfterEach | ");
        System.out.println("Outer AfterEach executed");
    }

    @Test
    void outerTestOne() {
        lifecycleLog.append("Outer Test1 | ");
        System.out.println("Outer Test1 executed");
        assertTrue(true);
    }

    @Test
    void outerTestTwo() {
        lifecycleLog.append("Outer Test2 | ");
        System.out.println("Outer Test2 executed");
        assertTrue(true);
    }

    @Nested
    class InnerClassWithoutPerClass {

        @BeforeEach
        void beforeEachInner() {
            lifecycleLog.append("Inner BeforeEach | ");
            System.out.println("Inner BeforeEach executed");
        }

        @AfterEach
        void afterEachInner() {
            lifecycleLog.append("Inner AfterEach | ");
            System.out.println("Inner AfterEach executed");
        }

        // This would cause an error before Java 16 if uncommented:
        //   @BeforeAll
        //   static void beforeAllInner() {
        //    }

        @Test
        void innerTestOne() {
            lifecycleLog.append("Inner Test1 | ");
            System.out.println("Inner Test1 executed");
            assertTrue(true);
        }

        @Test
        void innerTestTwo() {
            lifecycleLog.append("Inner Test2 | ");
            System.out.println("Inner Test2 executed");
            assertTrue(true);
        }

        @Nested
        class DeepNestedClass {
            @BeforeEach
            void beforeEachDeep() {
                lifecycleLog.append("Deep BeforeEach | ");
                System.out.println("Deep BeforeEach executed");
            }

            @AfterEach
            void afterEachDeep() {
                lifecycleLog.append("Deep AfterEach | ");
                System.out.println("Deep AfterEach executed");
            }

            @Test
            void deepTest() {
                lifecycleLog.append("Deep Test | ");
                System.out.println("Deep Test executed");
                assertTrue(true);
            }
        }
    }

    @Nested
    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    class InnerClassWithPerClass {
        private int testCounter = 0;

        @BeforeAll
        void beforeAllInner() {
            // can be non-static due to @TestInstance(PER_CLASS)
            lifecycleLog.append("PerClass Inner BeforeAll | ");
            System.out.println("PerClass Inner BeforeAll executed");
        }

        @AfterAll
        void afterAllInner() {
            lifecycleLog.append("PerClass Inner AfterAll | ");
            System.out.println("PerClass Inner AfterAll executed");
        }

        @BeforeEach
        void beforeEachInner() {
            testCounter++;
            lifecycleLog.append("PerClass Inner BeforeEach | ");
            System.out.println("PerClass Inner BeforeEach executed - Counter: " + testCounter);
        }

        @AfterEach
        void afterEachInner() {
            lifecycleLog.append("PerClass Inner AfterEach | ");
            System.out.println("PerClass Inner AfterEach executed");
        }

        @Test
        void perClassInnerTestOne() {
            lifecycleLog.append("PerClass Inner Test1 | ");
            System.out.println("PerClass Inner Test1 executed - Counter: " + testCounter);
            assertTrue(testCounter >= 1);
        }

        @Test
        void perClassInnerTestTwo() {
            lifecycleLog.append("PerClass Inner Test2 | ");
            System.out.println("PerClass Inner Test2 executed - Counter: " + testCounter);
            assertTrue(testCounter >= 2);
        }
    }
}

Output

D:\example-projects\junit-5\nested-classes\junit-5-nested-class-lifecycle-annotations>mvn test -Dtest=NestedClassLifecycleAnnotationsTest
[INFO] Scanning for projects...
[INFO]
[INFO] ------< com.logicbig.example:nested-class-lifecycle-annotations >-------
[INFO] Building nested-class-lifecycle-annotations 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ nested-class-lifecycle-annotations ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\junit-5\nested-classes\junit-5-nested-class-lifecycle-annotations\src\main\resources
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ nested-class-lifecycle-annotations ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ nested-class-lifecycle-annotations ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\junit-5\nested-classes\junit-5-nested-class-lifecycle-annotations\src\test\resources
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ nested-class-lifecycle-annotations ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- surefire:3.5.0:test (default-test) @ nested-class-lifecycle-annotations ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
Outer BeforeAll executed
Outer BeforeEach executed
Outer Test1 executed
Outer AfterEach executed
Outer BeforeEach executed
Outer Test2 executed
Outer AfterEach executed
PerClass Inner BeforeAll executed
Outer BeforeEach executed
PerClass Inner BeforeEach executed - Counter: 1
PerClass Inner Test1 executed - Counter: 1
PerClass Inner AfterEach executed
Outer AfterEach executed
Outer BeforeEach executed
PerClass Inner BeforeEach executed - Counter: 2
PerClass Inner Test2 executed - Counter: 2
PerClass Inner AfterEach executed
Outer AfterEach executed
PerClass Inner AfterAll executed
Outer BeforeEach executed
Inner BeforeEach executed
Inner Test1 executed
Inner AfterEach executed
Outer AfterEach executed
Outer BeforeEach executed
Inner BeforeEach executed
Inner Test2 executed
Inner AfterEach executed
Outer AfterEach executed
Outer BeforeEach executed
Inner BeforeEach executed
Deep BeforeEach executed
Deep Test executed
Deep AfterEach executed
Inner AfterEach executed
Outer AfterEach executed
Outer AfterAll executed
Final Lifecycle Log: Outer BeforeAll | Outer BeforeEach | Outer Test1 | Outer AfterEach | Outer BeforeEach | Outer Test2 | Outer AfterEach | PerClass Inner BeforeAll | Outer BeforeEach | PerClass Inner BeforeEach | PerClass Inner Test1 | PerClass Inner AfterEach | Outer AfterEach | Outer BeforeEach | PerClass Inner BeforeEach | PerClass Inner Test2 | PerClass Inner AfterEach | Outer AfterEach | PerClass Inner AfterAll | Outer BeforeEach | Inner BeforeEach | Inner Test1 | Inner AfterEach | Outer AfterEach | Outer BeforeEach | Inner BeforeEach | Inner Test2 | Inner AfterEach | Outer AfterEach | Outer BeforeEach | Inner BeforeEach | Deep BeforeEach | Deep Test | Deep AfterEach | Inner AfterEach | Outer AfterEach | Outer AfterAll |
[INFO] +--com.logicbig.example.NestedClassLifecycleAnnotationsTest - 0.167 ss
[INFO] | +-- [OK] outerTestOne - 0.044 ss
[INFO] | '-- [OK] outerTestTwo - 0.003 ss
[INFO] +--.--com.logicbig.example.NestedClassLifecycleAnnotationsTest$InnerClassWithPerClass - 0.015 ss
[INFO] | | +-- [OK] perClassInnerTestOne - 0.004 ss
[INFO] | | '-- [OK] perClassInnerTestTwo - 0.002 ss
[INFO] +--.--com.logicbig.example.NestedClassLifecycleAnnotationsTest$InnerClassWithoutPerClass - 0.015 ss
[INFO] | | +-- [OK] innerTestOne - 0.002 ss
[INFO] | | '-- [OK] innerTestTwo - 0.002 ss
[INFO] | '-----com.logicbig.example.NestedClassLifecycleAnnotationsTest$InnerClassWithoutPerClass$DeepNestedClass - 0.012 ss
[INFO] | '-- [OK] deepTest - 0.003 ss
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.968 s
[INFO] Finished at: 2025-12-06T09:57:40+08:00
[INFO] ------------------------------------------------------------------------
JDK 17.0.4.1

Example Project

Dependencies and Technologies Used:

  • junit-jupiter-engine 6.0.1 (Module "junit-jupiter-engine" of JUnit)
     Version Compatibility: 5.0.0 - 6.0.1Version List
    ×

    Version compatibilities of junit-jupiter-engine with this example:

    • 5.0.0
    • 5.0.1
    • 5.0.2
    • 5.0.3
    • 5.1.0
    • 5.1.1
    • 5.2.0
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.4.0
    • 5.4.1
    • 5.4.2
    • 5.5.0
    • 5.5.1
    • 5.5.2
    • 5.6.0
    • 5.6.1
    • 5.6.2
    • 5.6.3
    • 5.7.0
    • 5.7.1
    • 5.7.2
    • 5.8.0
    • 5.8.1
    • 5.8.2
    • 5.9.0
    • 5.9.1
    • 5.9.2
    • 5.9.3
    • 5.10.0
    • 5.10.1
    • 5.10.2
    • 5.10.3
    • 5.10.4
    • 5.10.5
    • 5.11.0
    • 5.11.1
    • 5.11.2
    • 5.11.3
    • 5.11.4
    • 5.12.0
    • 5.12.1
    • 5.12.2
    • 5.13.0
    • 5.13.1
    • 5.13.2
    • 5.13.3
    • 5.13.4
    • 5.14.0
    • 5.14.1
    • 6.0.0
    • 6.0.1

    Versions in green have been tested.

  • JDK 25
  • Maven 3.9.11

Nested Class Lifecycle Annotations Select All Download
  • junit-5-nested-class-lifecycle-annotations
    • src
      • test
        • java
          • com
            • logicbig
              • example
                • NestedClassLifecycleAnnotationsTest.java

    See Also