Close

JUnit 5 - ExtensionContext - Hierarchy & Identification

[Last Updated: Jan 3, 2026]

The JUnit 5 extension model provides a hierarchical view of test execution through the ExtensionContext interface. Understanding this hierarchy is essential for extensions that need to operate at specific levels of the test tree or share information between related test components.

Need for Hierarchy Navigation

Extensions often need to understand their position in the test execution tree to make context-aware decisions. For example, an extension might need to apply different behaviors for nested test classes versus top-level classes, or share configuration data between a test class and its nested test methods. Without proper hierarchy navigation, extensions would treat all test executions uniformly, missing opportunities for targeted optimizations and contextual configurations.

Navigation Methods

Optional<ExtensionContext> getParent()

Returns the parent context, allowing upward traversal of the test tree. Essential for extensions that need to access or modify parent-level state.

ExtensionContext getRoot()

Returns the root context of the test tree. Useful for extensions that need to access global configuration or establish baseline behavior.

List<Class<?>> getEnclosingTestClasses()

Returns the hierarchy of enclosing test classes from outermost to innermost. This is particularly useful for nested test classes where you need to know the complete class hierarchy.

Identification Methods

String getUniqueId()

Returns a technical identifier for the current context node. This ID follows a specific format and can be used for debugging, logging, or creating deterministic keys for storage.

String getDisplayName()

Returns a human-readable name for the current context. Ideal for reporting, logging, and user-facing messages where readability matters more than technical precision.

Understanding the Test Hierarchy

The test execution tree follows this typical structure:

  • Root: The top-level container (engine or suite)
  • Class Level: Test class contexts containing method contexts
  • Method Level: Individual test method executions
  • Nested Contexts: Additional levels for parameterized tests, dynamic tests, or nested classes

Each level provides appropriate context for extensions to operate at the right granularity. The getEnclosingTestClasses() method is especially valuable for understanding nested class relationships.

Use Cases

  • Conditional Behavior: Apply different logging levels based on test depth or enclosing class hierarchy
  • Data Sharing: Share setup data between parent and child contexts or across nested classes
  • Resource Management: Initialize resources at appropriate class level in nested hierarchies
  • Dynamic Configuration: Adjust timeout settings based on test nesting level or enclosing classes
  • Hierarchical Test Data: Load different test data based on the complete class hierarchy
  • Cross-Class Configuration: Apply configurations that span multiple levels of nested classes

Example

Extension Implementation

package com.logicbig.example;

import org.junit.jupiter.api.extension.*;
import java.util.List;

public class HierarchyAwareExtension implements BeforeEachCallback, BeforeAllCallback {

    @Override
    public void beforeAll(ExtensionContext context) {
        System.out.println("\n=== BEFORE ALL ===");
        printContextInfo("Class Level", context);
    }

    @Override
    public void beforeEach(ExtensionContext context) {
        System.out.println("\n=== BEFORE EACH ===");
        printContextInfo("Method Level", context);

        // Show parent hierarchy
        showParentHierarchy(context);

        // Show enclosing classes hierarchy
        showEnclosingClasses(context);

        // Show hierarchy depth
        int depth = calculateHierarchyDepth(context);
        System.out.println("Hierarchy Depth: " + depth);
    }

    private void printContextInfo(String phase, ExtensionContext context) {
        System.out.println("Phase: " + phase);
        System.out.println("Display Name: " + context.getDisplayName());
        System.out.println("Unique ID: " + context.getUniqueId());
        System.out.println("---");
    }

    private void showParentHierarchy(ExtensionContext context) {
        System.out.println("Parent Hierarchy:");
        System.out.println("  Current -> " + context.getDisplayName());

        ExtensionContext current = context;
        int level = 1;
        while (current.getParent().isPresent()) {
            current = current.getParent().get();
            System.out.println("  Parent Level " + level + " -> " + current.getDisplayName());
            level++;
        }

        System.out.println("  Root -> " + context.getRoot().getDisplayName());
    }

    private void showEnclosingClasses(ExtensionContext context) {
        List<Class<?>> enclosingClasses = context.getEnclosingTestClasses();
        if (!enclosingClasses.isEmpty()) {
            System.out.println("Enclosing Classes (outermost to innermost):");
            for (int i = 0; i < enclosingClasses.size(); i++) {
                System.out.println("  Level " + i + ": " + enclosingClasses.get(i).getSimpleName());
            }
        } else {
            System.out.println("No enclosing classes (top-level test)");
        }
    }

    private int calculateHierarchyDepth(ExtensionContext context) {
        int depth = 0;
        ExtensionContext current = context;

        while (current.getParent().isPresent()) {
            depth++;
            current = current.getParent().get();
        }

        return depth;
    }
}

Test Class

package com.logicbig.example;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(HierarchyAwareExtension.class)
public class HierarchyExtensionTest {

    @Test
    void topLevelTest1() {
        System.out.println("Executing topLevelTest1");
        // Simulate some work
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    @Test
    void topLevelTest2() {
        System.out.println("Executing topLevelTest2");
        // Simulate different work duration
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

Output

$ mvn test -Dtest=HierarchyExtensionTest
[INFO] Scanning for projects...
[INFO]
[INFO] ------< com.logicbig.example:junit-5-extension-context-hierarchy >------
[INFO] Building junit-5-extension-context-hierarchy 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ junit-5-extension-context-hierarchy ---
[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\junit-5-extension-context\junit-5-extension-context-hierarchy\src\main\resources
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ junit-5-extension-context-hierarchy ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ junit-5-extension-context-hierarchy ---
[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\junit-5-extension-context\junit-5-extension-context-hierarchy\src\test\resources
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ junit-5-extension-context-hierarchy ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- surefire:3.5.0:test (default-test) @ junit-5-extension-context-hierarchy ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------

=== BEFORE ALL ===
Phase: Class Level
Display Name: HierarchyExtensionTest
Unique ID: [engine:junit-jupiter]/[class:com.logicbig.example.HierarchyExtensionTest]
---

=== BEFORE EACH ===
Phase: Method Level
Display Name: topLevelTest1()
Unique ID: [engine:junit-jupiter]/[class:com.logicbig.example.HierarchyExtensionTest]/[method:topLevelTest1()]
---
Parent Hierarchy:
Current -> topLevelTest1()
Parent Level 1 -> HierarchyExtensionTest
Parent Level 2 -> JUnit Jupiter
Root -> JUnit Jupiter
No enclosing classes (top-level test)
Hierarchy Depth: 2
Executing topLevelTest1

=== BEFORE EACH ===
Phase: Method Level
Display Name: topLevelTest2()
Unique ID: [engine:junit-jupiter]/[class:com.logicbig.example.HierarchyExtensionTest]/[method:topLevelTest2()]
---
Parent Hierarchy:
Current -> topLevelTest2()
Parent Level 1 -> HierarchyExtensionTest
Parent Level 2 -> JUnit Jupiter
Root -> JUnit Jupiter
No enclosing classes (top-level test)
Hierarchy Depth: 2
Executing topLevelTest2
[INFO] +--com.logicbig.example.HierarchyExtensionTest - 0.423 ss
[INFO] | +-- [OK] topLevelTest1 - 0.169 ss
[INFO] | '-- [OK] topLevelTest2 - 0.218 ss
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.259 s
[INFO] Finished at: 2026-01-02T16:36:41+08:00
[INFO] ------------------------------------------------------------------------

Nested Test Class

package com.logicbig.example;

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(HierarchyAwareExtension.class)
public class NestedHierarchyTest {

    @Test
    void outerTest() {
        System.out.println("Executing outerTest");
    }

    @Nested
    class FirstNestedLevel {

        @Test
        void firstLevelTest() {
            System.out.println("Executing firstLevelTest");
        }

        @Nested
        class SecondNestedLevel {

            @Test
            void secondLevelTest() {
                System.out.println("Executing secondLevelTest");
            }
        }
    }
}

Output

$ mvn test -Dtest=NestedHierarchyTest
[INFO] Scanning for projects...
[INFO]
[INFO] ------< com.logicbig.example:junit-5-extension-context-hierarchy >------
[INFO] Building junit-5-extension-context-hierarchy 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ junit-5-extension-context-hierarchy ---
[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\junit-5-extension-context\junit-5-extension-context-hierarchy\src\main\resources
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ junit-5-extension-context-hierarchy ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ junit-5-extension-context-hierarchy ---
[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\junit-5-extension-context\junit-5-extension-context-hierarchy\src\test\resources
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ junit-5-extension-context-hierarchy ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- surefire:3.5.0:test (default-test) @ junit-5-extension-context-hierarchy ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------

=== BEFORE ALL ===
Phase: Class Level
Display Name: NestedHierarchyTest
Unique ID: [engine:junit-jupiter]/[class:com.logicbig.example.NestedHierarchyTest]
---

=== BEFORE EACH ===
Phase: Method Level
Display Name: outerTest()
Unique ID: [engine:junit-jupiter]/[class:com.logicbig.example.NestedHierarchyTest]/[method:outerTest()]
---
Parent Hierarchy:
Current -> outerTest()
Parent Level 1 -> NestedHierarchyTest
Parent Level 2 -> JUnit Jupiter
Root -> JUnit Jupiter
No enclosing classes (top-level test)
Hierarchy Depth: 2
Executing outerTest

=== BEFORE ALL ===
Phase: Class Level
Display Name: FirstNestedLevel
Unique ID: [engine:junit-jupiter]/[class:com.logicbig.example.NestedHierarchyTest]/[nested-class:FirstNestedLevel]
---

=== BEFORE EACH ===
Phase: Method Level
Display Name: firstLevelTest()
Unique ID: [engine:junit-jupiter]/[class:com.logicbig.example.NestedHierarchyTest]/[nested-class:FirstNestedLevel]/[method:firstLevelTest()]
---
Parent Hierarchy:
Current -> firstLevelTest()
Parent Level 1 -> FirstNestedLevel
Parent Level 2 -> NestedHierarchyTest
Parent Level 3 -> JUnit Jupiter
Root -> JUnit Jupiter
Enclosing Classes (outermost to innermost):
Level 0: NestedHierarchyTest
Hierarchy Depth: 3
Executing firstLevelTest

=== BEFORE ALL ===
Phase: Class Level
Display Name: SecondNestedLevel
Unique ID: [engine:junit-jupiter]/[class:com.logicbig.example.NestedHierarchyTest]/[nested-class:FirstNestedLevel]/[nested-class:SecondNestedLevel]
---

=== BEFORE EACH ===
Phase: Method Level
Display Name: secondLevelTest()
Unique ID: [engine:junit-jupiter]/[class:com.logicbig.example.NestedHierarchyTest]/[nested-class:FirstNestedLevel]/[nested-class:SecondNestedLevel]/[method:secondLevelTest()]
---
Parent Hierarchy:
Current -> secondLevelTest()
Parent Level 1 -> SecondNestedLevel
Parent Level 2 -> FirstNestedLevel
Parent Level 3 -> NestedHierarchyTest
Parent Level 4 -> JUnit Jupiter
Root -> JUnit Jupiter
Enclosing Classes (outermost to innermost):
Level 0: NestedHierarchyTest
Level 1: FirstNestedLevel
Hierarchy Depth: 4
Executing secondLevelTest
[INFO] +--com.logicbig.example.NestedHierarchyTest - 0.108 ss
[INFO] | '-- [OK] outerTest - 0.048 ss
[INFO] +--.--com.logicbig.example.NestedHierarchyTest$FirstNestedLevel - 0.025 ss
[INFO] | | '-- [OK] firstLevelTest - 0.008 ss
[INFO] | '-----com.logicbig.example.NestedHierarchyTest$FirstNestedLevel$SecondNestedLevel - 0.008 ss
[INFO] | '-- [OK] secondLevelTest - 0.002 ss
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.251 s
[INFO] Finished at: 2026-01-02T16:36:55+08:00
[INFO] ------------------------------------------------------------------------

Conclusion

The output demonstrates how the HierarchyAwareExtension effectively navigates and identifies different levels of the test execution tree. The extension correctly identifies the root context, distinguishes between class-level and method-level executions, and properly handles nested test structures using both parent navigation and enclosing class hierarchy. The getEnclosingTestClasses() method reveals the complete class hierarchy for nested tests, while getUniqueId() shows the technical relationships between contexts. This comprehensive hierarchical awareness enables extensions to make intelligent decisions based on execution scope, such as applying different timeout policies at different levels, sharing resources appropriately within nested class structures, and loading context-specific configurations based on the complete test hierarchy.

Example Project

Dependencies and Technologies Used:

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

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

    • 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

JUnit 5 - Extension Context Hierarchy & Identification Select All Download
  • junit-5-extension-context-hierarchy
    • src
      • test
        • java
          • com
            • logicbig
              • example
                • HierarchyExtensionTest.java

    See Also