diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java
index c3863a049342..eb95daab0237 100644
--- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java
+++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java
@@ -33,6 +33,10 @@ void useResourceLock(TestDescriptor testDescriptor, ResourceLock resourceLock) {
	void removeResourceLock(

diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.2.adoc
index 36d267b0e339..fcf7313a6354 100644
--- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.2.adoc
+++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.2.adoc
@@ -16,7 +16,9 @@ on GitHub.
 [[release-notes-5.11.2-junit-platform-bug-fixes]]
 ==== Bug Fixes

-* ❓
+* Fix regression in parallel execution that was introduced in 5.10.4/5.11.1 regarding
+  global read-write locks. When such a lock was declared on descendants of top-level nodes
+  in the test tree, such as Cucumber scenarios, test execution failed.

 [[release-notes-5.11.2-junit-platform-deprecations-and-breaking-changes]]
 ==== Deprecations and Breaking Changes
diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java
index c3863a049342..eb95daab0237 100644
--- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java
+++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java
@@ -33,6 +33,10 @@ void useResourceLock(TestDescriptor testDescriptor, ResourceLock resourceLock) {
 		resourceLocksByTestDescriptor.put(testDescriptor, resourceLock);
 	}

+	void removeResourceLock(TestDescriptor testDescriptor) {
+		resourceLocksByTestDescriptor.remove(testDescriptor);
+	}
+
 	Optional<ExecutionMode> getForcedExecutionMode(TestDescriptor testDescriptor) {
 		return testDescriptor.getParent().flatMap(this::lookupExecutionModeForcedByAncestor);
 	}
diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java
index 8e47d006849c..ada030923b76 100644
--- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java
+++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java
@@ -50,6 +50,12 @@ NodeExecutionAdvisor walk(TestDescriptor rootDescriptor) {

 	private void walk(TestDescriptor globalLockDescriptor, TestDescriptor testDescriptor,
 			NodeExecutionAdvisor advisor) {
+
+		if (advisor.getResourceLock(globalLockDescriptor) == globalReadWriteLock) {
+			// Global read-write lock is already being enforced, so no additional locks are needed
+			return;
+		}
+
 		Set<ExclusiveResource> exclusiveResources = getExclusiveResources(testDescriptor);
 		if (exclusiveResources.isEmpty()) {
 			if (globalLockDescriptor.equals(testDescriptor)) {
@@ -73,7 +79,12 @@ private void walk(TestDescriptor globalLockDescriptor, TestDescriptor testDescri
 				});
 			}
 			if (allResources.contains(GLOBAL_READ_WRITE)) {
-				forceDescendantExecutionModeRecursively(advisor, globalLockDescriptor);
+				advisor.forceDescendantExecutionMode(globalLockDescriptor, SAME_THREAD);
+				doForChildrenRecursively(globalLockDescriptor, child -> {
+					advisor.forceDescendantExecutionMode(child, SAME_THREAD);
+					// Remove any locks that may have been set for siblings or their descendants
+					advisor.removeResourceLock(child);
+				});
 				advisor.useResourceLock(globalLockDescriptor, globalReadWriteLock);
 			}
 			else {
@@ -94,8 +105,7 @@ private void forceDescendantExecutionModeRecursively(NodeExecutionAdvisor adviso
 	}

 	private boolean isReadOnly(Set<ExclusiveResource> exclusiveResources) {
-		return exclusiveResources.stream().allMatch(
-			exclusiveResource -> exclusiveResource.getLockMode() == ExclusiveResource.LockMode.READ);
+		return exclusiveResources.stream().allMatch(it -> it.getLockMode() == ExclusiveResource.LockMode.READ);
 	}

 	private Set<ExclusiveResource> getExclusiveResources(TestDescriptor testDescriptor) {

Skip processing nodes when a global read-write lock is already being enforced, and when enforcing a global read-write lock, remove any existing locks from descendants.

Prompt:
The following is the text of a github issue and related comments for this repository:

Declaring the global read-write lock on a leaf node should not fail execution
I've upgraded my Cucumber test framework with JUnit 5.11.1 and I get a new error:
```
[ERROR]   Task was deferred but should have been executed synchronously: NodeTestTask [PickleDescriptor: [engine:junit-platform-suite]/[suite:com.ionos.cloud.cucumber.ml.MlApiTest]/[engine:cucumber]/[feature:classpath%3Afeatures%2FModel%2FModelLB.feature]/[scenario:26]]

```
All I can say is that inside the feature file I have two scenarios: one marked with `@isolated`  global read write exclusive resource and another scenario is marked with a simple read write exclusive resource.

Reverting to JUnit 5.11.0 works without any error !

I'm sorry I cannot give more details. Maybe I will try to isolate it to a more detailed scenario when I'll have more time.

Resolve the issue described above. Do not modify tests or user code. The issue can be resolved by modifying the actual library/application code. Make the minimal changes to resolve the issue while adhering to the specification and coding style of the codebase.

This can be reproduced with https://github.com/mpkorstanje/junit5-locks/tree/main

Feature: example

  @isolated
  Scenario: a
    When I wait 1 hour

  @reads-and-writes-system-properties
  Scenario: b
    When I wait 1 hour
cucumber.execution.parallel.enabled=true
cucumber.execution.parallel.config.strategy=fixed
cucumber.execution.parallel.config.fixed.parallelism=2
cucumber.execution.exclusive-resources.isolated.read-write=org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY
cucumber.execution.exclusive-resources.reads-and-writes-system-properties.read-write=java.lang.System.properties
package io.cucumber.skeleton;

import io.cucumber.java.en.When;

import java.util.concurrent.TimeUnit;

public class StepDefinitions {
    @When("I wait {int} hour")
    public void iWaitHour(int arg0) throws InterruptedException {
        TimeUnit.SECONDS.sleep(arg0);
    }
}
Unlike with JUnit Jupiter the ExclusiveResource.GLOBAL_KEY is not used at top level:

example
 - a  <- locks ExclusiveResource.GLOBAL_KEY
 - b <-  locks java.lang.System.properties
Prior to executing b, the nop lock from example and the exclusive resource from a is still held in the thread locks.

resourceLock = {SingleLock@3016} "SingleLock [resource = ExclusiveResource [key = 'java.lang.System.properties', lockMode = READ_WRITE]]"
 resources = {Collections$SingletonList@3025}  size = 1
  0 = {ExclusiveResource@3029} "ExclusiveResource [key = 'java.lang.System.properties', lockMode = READ_WRITE]"
 lock = {ReentrantReadWriteLock$WriteLock@3026} "java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock@2c79993b[Unlocked]"
threadLock = {ForkJoinPoolHierarchicalTestExecutorService$ThreadLock@2471}
 locks = {ArrayDeque@3023}  size = 2
  0 = {SingleLock@2677} "SingleLock [resource = ExclusiveResource [key = 'org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY', lockMode = READ_WRITE]]"
  1 = {NopLock@2217} "NopLock []"
So then ResourceLock.isCompatible returns false because

junit5/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ResourceLock.java

Lines 81 to 85 in d013085

 boolean isGlobalReadLock = ownResources.size() == 1
 		&& ExclusiveResource.GLOBAL_READ.equals(ownResources.get(0));
 if ((!isGlobalReadLock && other.isExclusive()) || this.isExclusive()) {
 	return false;
 }
And it is worth noting that the comment about the ExclusiveResource.GLOBAL_KEY only applies to JUnit Jupiter.

junit5/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ResourceLock.java

Lines 78 to 80 in d013085

 // The global read lock (which is always on direct children of the engine node)
 // needs special treatment so that it is compatible with the first write lock
 // (which may be on a test method).

 Does Cucumber somehow bypass NodeTreeWalker which ensures the global lock is acquired at the top level?

 Cucumber does not. I'd say that the CucumberTestEngine is fairly unremarkable in that regard.

The difference is that JUnits @Isolated can only be applied at class level. And classes are always the children of a test engine. Where as in Cucumber the the global lock is applied at what would be JUnits method level. At a glance this seems to run counter to assumptions with which the NodeTreeWalker was written. I'd have to debug it a bit more to be certain but I haven't got a long enough stretch of time available for that right now.

Note: It doesn't look like any actual parallelism is needed to reproduce the problem. This will also produce the problem:

cucumber.execution.parallel.config.fixed.parallelism=1
cucumber.execution.parallel.config.fixed.max-pool-size=1

With some trickery it is possible effectively put @Isolated on a method. This results in the error message as with Cucumber.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.ResourceLock;

import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE;
import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY;

public class LockTest {

  @Test
  @ResourceLock(value = GLOBAL_KEY, mode = READ_WRITE) // effectively @Isolated
  void test1() throws InterruptedException {
    TimeUnit.SECONDS.sleep(1);
  }

  @Test
  @ResourceLock(value = "b", mode = READ_WRITE)
  void test2() throws InterruptedException {
    TimeUnit.SECONDS.sleep(1);
  }

}
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=1

diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java
index b556240bc295..4634c4c94f57 100644
--- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java
+++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java
@@ -18,6 +18,7 @@
 import static org.junit.jupiter.api.DynamicTest.dynamicTest;
 import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT;
 import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD;
+import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE;
 import static org.junit.jupiter.engine.Constants.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME;
 import static org.junit.jupiter.engine.Constants.DEFAULT_PARALLEL_EXECUTION_MODE;
 import static org.junit.jupiter.engine.Constants.PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME;
@@ -25,6 +26,7 @@
 import static org.junit.jupiter.engine.Constants.PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME;
 import static org.junit.jupiter.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME;
 import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement;
+import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY;
 import static org.junit.platform.testkit.engine.EventConditions.container;
 import static org.junit.platform.testkit.engine.EventConditions.event;
 import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully;
@@ -65,6 +67,8 @@
 import org.junit.jupiter.api.parallel.Execution;
 import org.junit.jupiter.api.parallel.Isolated;
 import org.junit.jupiter.api.parallel.ResourceLock;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 import org.junit.platform.engine.TestDescriptor;
 import org.junit.platform.engine.discovery.ClassSelector;
 import org.junit.platform.engine.discovery.DiscoverySelectors;
@@ -73,6 +77,7 @@
 import org.junit.platform.testkit.engine.EngineExecutionResults;
 import org.junit.platform.testkit.engine.EngineTestKit;
 import org.junit.platform.testkit.engine.Event;
+import org.junit.platform.testkit.engine.Events;

 /**
  * @since 1.3
@@ -82,7 +87,7 @@ class ParallelExecutionIntegrationTests {

 	@Test
 	void successfulParallelTest(TestReporter reporter) {
-		var events = executeConcurrently(3, SuccessfulParallelTestCase.class);
+		var events = executeConcurrentlySuccessfully(3, SuccessfulParallelTestCase.class).list();

 		var startedTimestamps = getTimestampsFor(events, event(test(), started()));
 		var finishedTimestamps = getTimestampsFor(events, event(test(), finishedSuccessfully()));
@@ -98,13 +103,13 @@ void successfulParallelTest(TestReporter reporter) {

 	@Test
 	void failingTestWithoutLock() {
-		var events = executeConcurrently(3, FailingWithoutLockTestCase.class);
+		var events = executeConcurrently(3, FailingWithoutLockTestCase.class).list();
 		assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).hasSize(2);
 	}

 	@Test
 	void successfulTestWithMethodLock() {
-		var events = executeConcurrently(3, SuccessfulWithMethodLockTestCase.class);
+		var events = executeConcurrentlySuccessfully(3, SuccessfulWithMethodLockTestCase.class).list();

 		assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3);
 		assertThat(ThreadReporter.getThreadNames(events)).hasSize(3);
@@ -112,7 +117,7 @@ void successfulTestWithMethodLock() {

 	@Test
 	void successfulTestWithClassLock() {
-		var events = executeConcurrently(3, SuccessfulWithClassLockTestCase.class);
+		var events = executeConcurrentlySuccessfully(3, SuccessfulWithClassLockTestCase.class).list();

 		assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3);
 		assertThat(ThreadReporter.getThreadNames(events)).hasSize(1);
@@ -120,7 +125,7 @@ void successfulTestWithClassLock() {

 	@Test
 	void testCaseWithFactory() {
-		var events = executeConcurrently(3, TestCaseWithTestFactory.class);
+		var events = executeConcurrentlySuccessfully(3, TestCaseWithTestFactory.class).list();

 		assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3);
 		assertThat(ThreadReporter.getThreadNames(events)).hasSize(1);
@@ -133,7 +138,7 @@ void customContextClassLoader() {
 		var smilingLoader = new URLClassLoader("(-:", new URL[0], ClassLoader.getSystemClassLoader());
 		currentThread.setContextClassLoader(smilingLoader);
 		try {
-			var events = executeConcurrently(3, SuccessfulWithMethodLockTestCase.class);
+			var events = executeConcurrentlySuccessfully(3, SuccessfulWithMethodLockTestCase.class).list();

 			assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3);
 			assertThat(ThreadReporter.getThreadNames(events)).hasSize(3);
@@ -146,7 +151,8 @@ void customContextClassLoader() {

 	@RepeatedTest(10)
 	void mixingClassAndMethodLevelLocks() {
-		var events = executeConcurrently(4, TestCaseWithSortedLocks.class, TestCaseWithUnsortedLocks.class);
+		var events = executeConcurrentlySuccessfully(4, TestCaseWithSortedLocks.class,
+			TestCaseWithUnsortedLocks.class).list();

 		assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(6);
 		assertThat(ThreadReporter.getThreadNames(events).count()).isLessThanOrEqualTo(2);
@@ -154,7 +160,7 @@ void mixingClassAndMethodLevelLocks() {

 	@RepeatedTest(10)
 	void locksOnNestedTests() {
-		var events = executeConcurrently(3, TestCaseWithNestedLocks.class);
+		var events = executeConcurrentlySuccessfully(3, TestCaseWithNestedLocks.class).list();

 		assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(6);
 		assertThat(ThreadReporter.getThreadNames(events)).hasSize(1);
@@ -162,7 +168,7 @@ void locksOnNestedTests() {

 	@Test
 	void afterHooksAreCalledAfterConcurrentDynamicTestsAreFinished() {
-		var events = executeConcurrently(3, ConcurrentDynamicTestCase.class);
+		var events = executeConcurrentlySuccessfully(3, ConcurrentDynamicTestCase.class).list();

 		assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(1);
 		var timestampedEvents = ConcurrentDynamicTestCase.events;
@@ -175,14 +181,14 @@ void afterHooksAreCalledAfterConcurrentDynamicTestsAreFinished() {
 	 */
 	@Test
 	void threadInterruptedByUserCode() {
-		var events = executeConcurrently(3, InterruptedThreadTestCase.class);
+		var events = executeConcurrentlySuccessfully(3, InterruptedThreadTestCase.class).list();

 		assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(4);
 	}

 	@Test
 	void executesTestTemplatesWithResourceLocksInSameThread() {
-		var events = executeConcurrently(2, ConcurrentTemplateTestCase.class);
+		var events = executeConcurrentlySuccessfully(2, ConcurrentTemplateTestCase.class).list();

 		assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(10);
 		assertThat(ThreadReporter.getThreadNames(events)).hasSize(1);
@@ -228,30 +234,22 @@ void executesMethodsInParallelIfEnabledViaConfigurationParameter() {

 	@Test
 	void canRunTestsIsolatedFromEachOther() {
-		var events = executeConcurrently(2, IsolatedTestCase.class);
-
-		assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty();
+		executeConcurrentlySuccessfully(2, IsolatedTestCase.class);
 	}

 	@Test
 	void canRunTestsIsolatedFromEachOtherWithNestedCases() {
-		var events = executeConcurrently(4, NestedIsolatedTestCase.class);
-
-		assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty();
+		executeConcurrentlySuccessfully(4, NestedIsolatedTestCase.class);
 	}

 	@Test
 	void canRunTestsIsolatedFromEachOtherAcrossClasses() {
-		var events = executeConcurrently(4, IndependentClasses.A.class, IndependentClasses.B.class);
-
-		assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty();
+		executeConcurrentlySuccessfully(4, IndependentClasses.A.class, IndependentClasses.B.class);
 	}

 	@RepeatedTest(10)
 	void canRunTestsIsolatedFromEachOtherAcrossClassesWithOtherResourceLocks() {
-		var events = executeConcurrently(4, IndependentClasses.B.class, IndependentClasses.C.class);
-
-		assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty();
+		executeConcurrentlySuccessfully(4, IndependentClasses.B.class, IndependentClasses.C.class);
 	}

 	@Test
@@ -262,9 +260,8 @@ void runsIsolatedTestsLastToMaximizeParallelism() {
 		);
 		Class<?>[] testClasses = { IsolatedTestCase.class, SuccessfulParallelTestCase.class };
 		var events = executeWithFixedParallelism(3, configParams, testClasses) //
-				.allEvents();
-
-		assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty();
+				.allEvents() //
+				.assertStatistics(it -> it.failed(0));

 		List<Event> parallelTestMethodEvents = events.reportingEntryPublished() //
 				.filter(e -> e.getTestDescriptor().getSource() //
@@ -283,6 +280,15 @@ void runsIsolatedTestsLastToMaximizeParallelism() {
 		assertThat(isolatedClassStart).isAfterOrEqualTo(parallelClassFinish);
 	}

+	@ParameterizedTest
+	@ValueSource(classes = { IsolatedMethodFirstTestCase.class, IsolatedMethodLastTestCase.class,
+			IsolatedNestedMethodFirstTestCase.class, IsolatedNestedMethodLastTestCase.class })
+	void canRunTestsIsolatedFromEachOtherWhenDeclaredOnMethodLevel(Class<?> testClass) {
+		List<Event> events = executeConcurrentlySuccessfully(1, testClass).list();
+
+		assertThat(ThreadReporter.getThreadNames(events)).hasSize(1);
+	}
+
 	@Isolated("testing")
 	static class IsolatedTestCase {
 		static AtomicInteger sharedResource;
@@ -355,6 +361,122 @@ void b() throws Exception {
 		}
 	}

+	@ExtendWith(ThreadReporter.class)
+	static class IsolatedMethodFirstTestCase {
+
+		static AtomicInteger sharedResource;
+		static CountDownLatch countDownLatch;
+
+		@BeforeAll
+		static void initialize() {
+			sharedResource = new AtomicInteger();
+			countDownLatch = new CountDownLatch(2);
+		}
+
+		@Test
+		@ResourceLock(value = GLOBAL_KEY, mode = READ_WRITE) // effectively @Isolated
+		void test1() throws InterruptedException {
+			incrementBlockAndCheck(sharedResource, countDownLatch);
+		}
+
+		@Test
+		@ResourceLock(value = "b", mode = READ_WRITE)
+		void test2() throws InterruptedException {
+			incrementBlockAndCheck(sharedResource, countDownLatch);
+		}
+	}
+
+	@ExtendWith(ThreadReporter.class)
+	static class IsolatedMethodLastTestCase {
+
+		static AtomicInteger sharedResource;
+		static CountDownLatch countDownLatch;
+
+		@BeforeAll
+		static void initialize() {
+			sharedResource = new AtomicInteger();
+			countDownLatch = new CountDownLatch(2);
+		}
+
+		@Test
+		@ResourceLock(value = "b", mode = READ_WRITE)
+		void test1() throws InterruptedException {
+			incrementBlockAndCheck(sharedResource, countDownLatch);
+		}
+
+		@Test
+		@ResourceLock(value = GLOBAL_KEY, mode = READ_WRITE) // effectively @Isolated
+		void test2() throws InterruptedException {
+			incrementBlockAndCheck(sharedResource, countDownLatch);
+		}
+	}
+
+	@ExtendWith(ThreadReporter.class)
+	static class IsolatedNestedMethodFirstTestCase {
+
+		static AtomicInteger sharedResource;
+		static CountDownLatch countDownLatch;
+
+		@BeforeAll
+		static void initialize() {
+			sharedResource = new AtomicInteger();
+			countDownLatch = new CountDownLatch(2);
+		}
+
+		@Nested
+		class Test1 {
+
+			@Test
+			@ResourceLock(value = GLOBAL_KEY, mode = READ_WRITE) // effectively @Isolated
+			void test1() throws InterruptedException {
+				incrementBlockAndCheck(sharedResource, countDownLatch);
+			}
+		}
+
+		@Nested
+		class Test2 {
+
+			@Test
+			@ResourceLock(value = "b", mode = READ_WRITE)
+			void test2() throws InterruptedException {
+				incrementBlockAndCheck(sharedResource, countDownLatch);
+			}
+		}
+	}
+
+	@ExtendWith(ThreadReporter.class)
+	static class IsolatedNestedMethodLastTestCase {
+
+		static AtomicInteger sharedResource;
+		static CountDownLatch countDownLatch;
+
+		@BeforeAll
+		static void initialize() {
+			sharedResource = new AtomicInteger();
+			countDownLatch = new CountDownLatch(2);
+		}
+
+		@Nested
+		class Test1 {
+
+			@Test
+			@ResourceLock(value = "b", mode = READ_WRITE)
+			void test1() throws InterruptedException {
+				incrementBlockAndCheck(sharedResource, countDownLatch);
+			}
+		}
+
+		@Nested
+		class Test2 {
+
+			@Test
+			@ResourceLock(value = GLOBAL_KEY, mode = READ_WRITE) // effectively @Isolated
+			void test2() throws InterruptedException {
+				incrementBlockAndCheck(sharedResource, countDownLatch);
+			}
+		}
+	}
+
 	static class IndependentClasses {
 		static AtomicInteger sharedResource = new AtomicInteger();
 		static CountDownLatch countDownLatch = new CountDownLatch(4);
@@ -416,11 +538,21 @@ private List<Instant> getTimestampsFor(List<Event> events, Condition<Event> cond
 		// @formatter:on
 	}

-	private List<Event> executeConcurrently(int parallelism, Class<?>... testClasses) {
+	private Events executeConcurrentlySuccessfully(int parallelism, Class<?>... testClasses) {
+		var events = executeConcurrently(parallelism, testClasses);
+		try {
+			return events.assertStatistics(it -> it.failed(0));
+		}
+		catch (AssertionError error) {
+			events.debug();
+			throw error;
+		}
+	}
+
+	private Events executeConcurrently(int parallelism, Class<?>... testClasses) {
 		Map<String, String> configParams = Map.of(DEFAULT_PARALLEL_EXECUTION_MODE, "concurrent");
 		return executeWithFixedParallelism(parallelism, configParams, testClasses) //
-				.allEvents() //
-				.list();
+				.allEvents();
 	}

 	private EngineExecutionResults executeWithFixedParallelism(int parallelism, Map<String, String> configParams,


diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java
index 810d180e3da8..89f7784d8c57 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java
@@ -280,10 +359,16 @@ private String getSentenceBeginning(Class<?> testClass) {
			List<Class<?>> remainingEnclosingInstanceTypes =
			String prefix =
			return

diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc
index 5d6ceaea5dc0..0abbde8c9af2 100644
--- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc
+++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc
@@ -83,7 +83,10 @@ JUnit repository on GitHub.
 [[release-notes-5.12.0-M1-junit-jupiter-bug-fixes]]
 ==== Bug Fixes

-* ❓
+* Provide _runtime_ enclosing types of `@Nested` test classes and contained test methods
+  to `DisplayNameGenerator` implementations. Prior to this change, such generators were
+  only able to access the enclosing class in which `@Nested` was declared, but they could
+  not access the concrete runtime type of the enclosing instance.

 [[release-notes-5.12.0-M1-junit-jupiter-deprecations-and-breaking-changes]]
 ==== Deprecations and Breaking Changes
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java
index 810d180e3da8..89f7784d8c57 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java
@@ -10,11 +10,15 @@

 package org.junit.jupiter.api;

+import static java.util.Collections.emptyList;
+import static org.apiguardian.api.API.Status.DEPRECATED;
+import static org.apiguardian.api.API.Status.EXPERIMENTAL;
 import static org.apiguardian.api.API.Status.STABLE;
 import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation;
 import static org.junit.platform.commons.support.ModifierSupport.isStatic;

 import java.lang.reflect.Method;
+import java.util.List;
 import java.util.Optional;
 import java.util.function.Predicate;

@@ -74,7 +78,8 @@ public interface DisplayNameGenerator {
 	/**
 	 * Generate a display name for the given top-level or {@code static} nested test class.
 	 *
-	 *
If it returns {@code null}, the default display name generator will be used instead.
+	 *
If this method returns {@code null}, the default display name
+	 * generator will be used instead.
 	 *
 	 * @param testClass the class to generate a name for; never {@code null}
 	 * @return the display name for the class; never blank
@@ -82,19 +87,52 @@ public interface DisplayNameGenerator {
 	String generateDisplayNameForClass(Class<?> testClass);

 	/**
-	 * Generate a display name for the given {@link Nested @Nested} inner test class.
+	 * Generate a display name for the given {@link Nested @Nested} inner test
+	 * class.
 	 *
-	 *
If it returns {@code null}, the default display name generator will be used instead.
+	 *
If this method returns {@code null}, the default display name
+	 * generator will be used instead.
 	 *
 	 * @param nestedClass the class to generate a name for; never {@code null}
 	 * @return the display name for the nested class; never blank
+	 * @deprecated in favor of {@link #generateDisplayNameForNestedClass(List, Class)}
 	 */
-	String generateDisplayNameForNestedClass(Class<?> nestedClass);
+	@API(status = DEPRECATED, since = "5.12")
+	@Deprecated
+	default String generateDisplayNameForNestedClass(Class<?> nestedClass) {
+		throw new UnsupportedOperationException(
+			"Implement generateDisplayNameForNestedClass(List<Class<?>>, Class<?>) instead");
+	}
+
+	/**
+	 * Generate a display name for the given {@link Nested @Nested} inner test
+	 * class.
+	 *
+	 *
If this method returns {@code null}, the default display name
+	 * generator will be used instead.
+	 *
+	 * @implNote The classes supplied as {@code enclosingInstanceTypes} may
+	 * differ from the classes returned from invocations of
+	 * {@link Class#getEnclosingClass()} — for example, when a nested test
+	 * class is inherited from a superclass.
+	 *
+	 * @param enclosingInstanceTypes the runtime types of the enclosing
+	 * instances for the test class, ordered from outermost to innermost,
+	 * excluding {@code nestedClass}; never {@code null}
+	 * @param nestedClass the class to generate a name for; never {@code null}
+	 * @return the display name for the nested class; never blank
+	 * @since 5.12
+	 */
+	@API(status = EXPERIMENTAL, since = "5.12")
+	default String generateDisplayNameForNestedClass(List<Class<?>> enclosingInstanceTypes, Class<?> nestedClass) {
+		return generateDisplayNameForNestedClass(nestedClass);
+	}

 	/**
 	 * Generate a display name for the given method.
 	 *
-	 *
If it returns {@code null}, the default display name generator will be used instead.
+	 *
If this method returns {@code null}, the default display name
+	 * generator will be used instead.
 	 *
 	 * @implNote The class instance supplied as {@code testClass} may differ from
 	 * the class returned by {@code testMethod.getDeclaringClass()} — for
@@ -103,8 +141,42 @@ public interface DisplayNameGenerator {
 	 * @param testClass the class the test method is invoked on; never {@code null}
 	 * @param testMethod method to generate a display name for; never {@code null}
 	 * @return the display name for the test; never blank
+	 * @deprecated in favor of {@link #generateDisplayNameForMethod(List, Class, Method)}
 	 */
-	String generateDisplayNameForMethod(Class<?> testClass, Method testMethod);
+	@API(status = DEPRECATED, since = "5.12")
+	@Deprecated
+	default String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
+		throw new UnsupportedOperationException(
+			"Implement generateDisplayNameForMethod(List<Class<?>>, Class<?>, Method) instead");
+	}
+
+	/**
+	 * Generate a display name for the given method.
+	 *
+	 *
If this method returns {@code null}, the default display name
+	 * generator will be used instead.
+	 *
+	 * @implNote The classes supplied as {@code enclosingInstanceTypes} may
+	 * differ from the classes returned from invocations of
+	 * {@link Class#getEnclosingClass()} — for example, when a nested test
+	 * class is inherited from a superclass. Similarly, the class instance
+	 * supplied as {@code testClass} may differ from the class returned by
+	 * {@code testMethod.getDeclaringClass()} — for example, when a test
+	 * method is inherited from a superclass.
+	 *
+	 * @param enclosingInstanceTypes the runtime types of the enclosing
+	 * instances for the test class, ordered from outermost to innermost,
+	 * excluding {@code testClass}; never {@code null}
+	 * @param testClass the class the test method is invoked on; never {@code null}
+	 * @param testMethod method to generate a display name for; never {@code null}
+	 * @return the display name for the test; never blank
+	 * @since 5.12
+	 */
+	@API(status = EXPERIMENTAL, since = "5.12")
+	default String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
+			Method testMethod) {
+		return generateDisplayNameForMethod(testClass, testMethod);
+	}

 	/**
 	 * Generate a string representation of the formal parameters of the supplied
@@ -142,12 +214,13 @@ public String generateDisplayNameForClass(Class<?> testClass) {
 		}

 		@Override
-		public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
+		public String generateDisplayNameForNestedClass(List<Class<?>> enclosingInstanceTypes, Class<?> nestedClass) {
 			return nestedClass.getSimpleName();
 		}

 		@Override
-		public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
+		public String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
+				Method testMethod) {
 			return testMethod.getName() + parameterTypesAsString(testMethod);
 		}
 	}
@@ -168,7 +241,8 @@ public Simple() {
 		}

 		@Override
-		public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
+		public String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
+				Method testMethod) {
 			String displayName = testMethod.getName();
 			if (hasParameters(testMethod)) {
 				displayName += ' ' + parameterTypesAsString(testMethod);
@@ -202,13 +276,15 @@ public String generateDisplayNameForClass(Class<?> testClass) {
 		}

 		@Override
-		public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
-			return replaceUnderscores(super.generateDisplayNameForNestedClass(nestedClass));
+		public String generateDisplayNameForNestedClass(List<Class<?>> enclosingInstanceTypes, Class<?> nestedClass) {
+			return replaceUnderscores(super.generateDisplayNameForNestedClass(enclosingInstanceTypes, nestedClass));
 		}

 		@Override
-		public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
-			return replaceUnderscores(super.generateDisplayNameForMethod(testClass, testMethod));
+		public String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
+				Method testMethod) {
+			return replaceUnderscores(
+				super.generateDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod));
 		}

 		private static String replaceUnderscores(String name) {
@@ -243,18 +319,21 @@ public String generateDisplayNameForClass(Class<?> testClass) {
 		}

 		@Override
-		public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
-			return getSentenceBeginning(nestedClass);
+		public String generateDisplayNameForNestedClass(List<Class<?>> enclosingInstanceTypes, Class<?> nestedClass) {
+			return getSentenceBeginning(enclosingInstanceTypes, nestedClass);
 		}

 		@Override
-		public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
-			return getSentenceBeginning(testClass) + getFragmentSeparator(testClass)
-					+ getGeneratorFor(testClass).generateDisplayNameForMethod(testClass, testMethod);
+		public String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
+				Method testMethod) {
+			return getSentenceBeginning(enclosingInstanceTypes, testClass) + getFragmentSeparator(testClass)
+					+ getGeneratorFor(testClass).generateDisplayNameForMethod(enclosingInstanceTypes, testClass,
+						testMethod);
 		}

-		private String getSentenceBeginning(Class<?> testClass) {
-			Class<?> enclosingClass = testClass.getEnclosingClass();
+		private String getSentenceBeginning(List<Class<?>> enclosingInstanceTypes, Class<?> testClass) {
+			Class<?> enclosingClass = enclosingInstanceTypes.isEmpty() ? null
+					: enclosingInstanceTypes.get(enclosingInstanceTypes.size() - 1);
 			boolean topLevelTestClass = (enclosingClass == null || isStatic(testClass));
 			Optional<String> displayName = findAnnotation(testClass, DisplayName.class)//
 					.map(DisplayName::value).map(String::trim);
@@ -280,10 +359,16 @@ private String getSentenceBeginning(Class<?> testClass) {
 					.filter(IndicativeSentences.class::equals)//
 					.isPresent();

-			String prefix = (buildPrefix ? getSentenceBeginning(enclosingClass) + getFragmentSeparator(testClass) : "");
+			List<Class<?>> remainingEnclosingInstanceTypes = enclosingInstanceTypes.isEmpty() ? emptyList()
+					: enclosingInstanceTypes.subList(0, enclosingInstanceTypes.size() - 1);
+
+			String prefix = (buildPrefix
+					? getSentenceBeginning(remainingEnclosingInstanceTypes, enclosingClass)
+							+ getFragmentSeparator(testClass)
+					: "");

-			return prefix + displayName.orElseGet(
-				() -> getGeneratorFor(testClass).generateDisplayNameForNestedClass(testClass));
+			return prefix + displayName.orElseGet(() -> getGeneratorFor(testClass).generateDisplayNameForNestedClass(
+				remainingEnclosingInstanceTypes, testClass));
 		}

 		/**
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java
index ebe3d127bf0c..76b65ef5e49a 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java
@@ -14,6 +14,7 @@

 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Method;
+import java.util.List;
 import java.util.Optional;
 import java.util.function.Function;
 import java.util.function.Supplier;
@@ -89,10 +90,10 @@ static String determineDisplayName(AnnotatedElement element, Supplier<String> di
 		return displayNameSupplier.get();
 	}

-	static String determineDisplayNameForMethod(Class<?> testClass, Method testMethod,
-			JupiterConfiguration configuration) {
+	static String determineDisplayNameForMethod(Supplier<List<Class<?>>> enclosingInstanceTypes, Class<?> testClass,
+			Method testMethod, JupiterConfiguration configuration) {
 		return determineDisplayName(testMethod,
-			createDisplayNameSupplierForMethod(testClass, testMethod, configuration));
+			createDisplayNameSupplierForMethod(enclosingInstanceTypes, testClass, testMethod, configuration));
 	}

 	static Supplier<String> createDisplayNameSupplierForClass(Class<?> testClass, JupiterConfiguration configuration) {
@@ -100,16 +101,16 @@ static Supplier<String> createDisplayNameSupplierForClass(Class<?> testClass, Ju
 			generator -> generator.generateDisplayNameForClass(testClass));
 	}

-	static Supplier<String> createDisplayNameSupplierForNestedClass(Class<?> testClass,
-			JupiterConfiguration configuration) {
+	static Supplier<String> createDisplayNameSupplierForNestedClass(Supplier<List<Class<?>>> enclosingInstanceTypes,
+			Class<?> testClass, JupiterConfiguration configuration) {
 		return createDisplayNameSupplier(testClass, configuration,
-			generator -> generator.generateDisplayNameForNestedClass(testClass));
+			generator -> generator.generateDisplayNameForNestedClass(enclosingInstanceTypes.get(), testClass));
 	}

-	private static Supplier<String> createDisplayNameSupplierForMethod(Class<?> testClass, Method testMethod,
-			JupiterConfiguration configuration) {
+	private static Supplier<String> createDisplayNameSupplierForMethod(Supplier<List<Class<?>>> enclosingInstanceTypes,
+			Class<?> testClass, Method testMethod, JupiterConfiguration configuration) {
 		return createDisplayNameSupplier(testClass, configuration,
-			generator -> generator.generateDisplayNameForMethod(testClass, testMethod));
+			generator -> generator.generateDisplayNameForMethod(enclosingInstanceTypes.get(), testClass, testMethod));
 	}

 	private static Supplier<String> createDisplayNameSupplier(Class<?> testClass, JupiterConfiguration configuration,
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java
index 525b58293709..52bf3c4ef8b9 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java
@@ -21,6 +21,7 @@
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.function.Supplier;

 import org.apiguardian.api.API;
 import org.junit.jupiter.api.extension.ExtensionContext;
@@ -59,9 +60,9 @@ public abstract class MethodBasedTestDescriptor extends JupiterTestDescriptor im
 	private final Set<TestTag> tags;

 	MethodBasedTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method testMethod,
-			JupiterConfiguration configuration) {
-		this(uniqueId, determineDisplayNameForMethod(testClass, testMethod, configuration), testClass, testMethod,
-			configuration);
+			Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
+		this(uniqueId, determineDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod, configuration),
+			testClass, testMethod, configuration);
 	}

 	MethodBasedTestDescriptor(UniqueId uniqueId, String displayName, Class<?> testClass, Method testMethod,
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java
index 6d4dc1e2d088..72d11ce5b09e 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java
@@ -19,6 +19,7 @@
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.Supplier;

 import org.apiguardian.api.API;
 import org.junit.jupiter.api.extension.TestInstances;
@@ -46,8 +47,10 @@ public class NestedClassTestDescriptor extends ClassBasedTestDescriptor {

 	public static final String SEGMENT_TYPE = "nested-class";

-	public NestedClassTestDescriptor(UniqueId uniqueId, Class<?> testClass, JupiterConfiguration configuration) {
-		super(uniqueId, testClass, createDisplayNameSupplierForNestedClass(testClass, configuration), configuration);
+	public NestedClassTestDescriptor(UniqueId uniqueId, Class<?> testClass,
+			Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
+		super(uniqueId, testClass,
+			createDisplayNameSupplierForNestedClass(enclosingInstanceTypes, testClass, configuration), configuration);
 	}

 	// --- TestDescriptor ------------------------------------------------------
@@ -62,7 +65,11 @@ public final Set<TestTag> getTags() {

 	@Override
 	public List<Class<?>> getEnclosingTestClasses() {
-		TestDescriptor parent = getParent().orElse(null);
+		return getEnclosingTestClasses(getParent().orElse(null));
+	}
+
+	@API(status = INTERNAL, since = "5.12")
+	public static List<Class<?>> getEnclosingTestClasses(TestDescriptor parent) {
 		if (parent instanceof ClassBasedTestDescriptor) {
 			ClassBasedTestDescriptor parentClassDescriptor = (ClassBasedTestDescriptor) parent;
 			List<Class<?>> result = new ArrayList<>(parentClassDescriptor.getEnclosingTestClasses());
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java
index e4643baa54de..f0d37814bbf2 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java
@@ -18,6 +18,7 @@
 import java.lang.reflect.Method;
 import java.net.URI;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Optional;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
@@ -63,8 +64,8 @@ public class TestFactoryTestDescriptor extends TestMethodTestDescriptor implemen
 	private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter();

 	public TestFactoryTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method testMethod,
-			JupiterConfiguration configuration) {
-		super(uniqueId, testClass, testMethod, configuration);
+			Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
+		super(uniqueId, testClass, testMethod, enclosingInstanceTypes, configuration);
 	}

 	// --- Filterable ----------------------------------------------------------
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java
index 423f2e3b9013..67fa136a52d1 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java
@@ -17,6 +17,8 @@
 import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder;

 import java.lang.reflect.Method;
+import java.util.List;
+import java.util.function.Supplier;

 import org.apiguardian.api.API;
 import org.junit.jupiter.api.TestInstance.Lifecycle;
@@ -75,8 +77,8 @@ public class TestMethodTestDescriptor extends MethodBasedTestDescriptor {
 	private final ReflectiveInterceptorCall<Method, Void> interceptorCall;

 	public TestMethodTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method testMethod,
-			JupiterConfiguration configuration) {
-		super(uniqueId, testClass, testMethod, configuration);
+			Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
+		super(uniqueId, testClass, testMethod, enclosingInstanceTypes, configuration);
 		this.interceptorCall = defaultInterceptorCall;
 	}

diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java
index f89c17b26a32..e592ba3a6326 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java
@@ -18,6 +18,7 @@
 import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
 import java.util.stream.Stream;

 import org.apiguardian.api.API;
@@ -46,8 +47,8 @@ public class TestTemplateTestDescriptor extends MethodBasedTestDescriptor implem
 	private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter();

 	public TestTemplateTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method templateMethod,
-			JupiterConfiguration configuration) {
-		super(uniqueId, testClass, templateMethod, configuration);
+			Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
+		super(uniqueId, testClass, templateMethod, enclosingInstanceTypes, configuration);
 	}

 	// --- Filterable ----------------------------------------------------------
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java
index 46d97088c358..0f809dcbde47 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java
@@ -12,6 +12,7 @@

 import static java.util.function.Predicate.isEqual;
 import static java.util.stream.Collectors.toCollection;
+import static org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor.getEnclosingTestClasses;
 import static org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests.isTestOrTestFactoryOrTestTemplateMethod;
 import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN;
 import static org.junit.platform.commons.support.ReflectionSupport.findMethods;
@@ -122,9 +123,9 @@ private ClassTestDescriptor newClassTestDescriptor(TestDescriptor parent, Class<
 	}

 	private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor parent, Class<?> testClass) {
-		return new NestedClassTestDescriptor(
-			parent.getUniqueId().append(NestedClassTestDescriptor.SEGMENT_TYPE, testClass.getSimpleName()), testClass,
-			configuration);
+		UniqueId uniqueId = parent.getUniqueId().append(NestedClassTestDescriptor.SEGMENT_TYPE,
+			testClass.getSimpleName());
+		return new NestedClassTestDescriptor(uniqueId, testClass, () -> getEnclosingTestClasses(parent), configuration);
 	}

 	private Resolution toResolution(Optional<? extends ClassBasedTestDescriptor> testDescriptor) {
diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java
index 7f76e2d30fce..9d5af96aa103 100644
--- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java
+++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java
@@ -166,8 +166,8 @@ private enum MethodType {
 		TEST(new IsTestMethod(), TestMethodTestDescriptor.SEGMENT_TYPE) {
 			@Override
 			protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method method,
-					JupiterConfiguration configuration) {
-				return new TestMethodTestDescriptor(uniqueId, testClass, method, configuration);
+					Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
+				return new TestMethodTestDescriptor(uniqueId, testClass, method, enclosingInstanceTypes, configuration);
 			}
 		},

@@ -176,8 +176,9 @@ protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class<?> testCl
 				TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE) {
 			@Override
 			protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method method,
-					JupiterConfiguration configuration) {
-				return new TestFactoryTestDescriptor(uniqueId, testClass, method, configuration);
+					Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
+				return new TestFactoryTestDescriptor(uniqueId, testClass, method, enclosingInstanceTypes,
+					configuration);
 			}
 		},

@@ -185,8 +186,9 @@ protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class<?> testCl
 				TestTemplateInvocationTestDescriptor.SEGMENT_TYPE) {
 			@Override
 			protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method method,
-					JupiterConfiguration configuration) {
-				return new TestTemplateTestDescriptor(uniqueId, testClass, method, configuration);
+					Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration) {
+				return new TestTemplateTestDescriptor(uniqueId, testClass, method, enclosingInstanceTypes,
+					configuration);
 			}
 		};

@@ -207,7 +209,7 @@ private Optional<TestDescriptor> resolve(List<Class<?>> enclosingClasses, Class<
 			}
 			return context.addToParent(() -> selectClass(enclosingClasses, testClass), //
 				parent -> Optional.of(
-					createTestDescriptor(createUniqueId(method, parent), testClass, method, configuration)));
+					createTestDescriptor((ClassBasedTestDescriptor) parent, testClass, method, configuration)));
 		}

 		private DiscoverySelector selectClass(List<Class<?>> enclosingClasses, Class<?> testClass) {
@@ -227,7 +229,7 @@ private Optional<TestDescriptor> resolveUniqueIdIntoTestDescriptor(UniqueId uniq
 					// @formatter:off
 					return methodFinder.findMethod(methodSpecPart, testClass)
 							.filter(methodPredicate)
-							.map(method -> createTestDescriptor(createUniqueId(method, parent), testClass, method, configuration));
+							.map(method -> createTestDescriptor((ClassBasedTestDescriptor) parent, testClass, method, configuration));
 					// @formatter:on
 				});
 			}
@@ -237,6 +239,12 @@ private Optional<TestDescriptor> resolveUniqueIdIntoTestDescriptor(UniqueId uniq
 			return Optional.empty();
 		}

+		private TestDescriptor createTestDescriptor(ClassBasedTestDescriptor parent, Class<?> testClass, Method method,
+				JupiterConfiguration configuration) {
+			UniqueId uniqueId = createUniqueId(method, parent);
+			return createTestDescriptor(uniqueId, testClass, method, parent::getEnclosingTestClasses, configuration);
+		}
+
 		private UniqueId createUniqueId(Method method, TestDescriptor parent) {
 			String methodId = String.format("%s(%s)", method.getName(),
 				ClassUtils.nullSafeToString(method.getParameterTypes()));
@@ -244,7 +252,7 @@ private UniqueId createUniqueId(Method method, TestDescriptor parent) {
 		}

 		protected abstract TestDescriptor createTestDescriptor(UniqueId uniqueId, Class<?> testClass, Method method,
-				JupiterConfiguration configuration);
+				Supplier<List<Class<?>>> enclosingInstanceTypes, JupiterConfiguration configuration);

 	}

Modify the display name utility methods to accept and use a list of enclosing instance types when generating display names for methods and nested classes.

Prompt:
The following is the text of a github issue and related comments for this repository:

`DisplayNameGenerator` cannot access runtime enclosing type for `@Nested` test class
## Overview

A `DisplayNameGenerator` can access the type of a `@Nested` test class as well as the enclosing class in which a `@Nested` test class is declared, but it cannot access the concrete runtime type of the enclosing instance for a `@Nested` test class.

When a `DisplayNameGenerator` is used to build hierarchical display names, this can lead to confusing results or even conflicting results depending on the structure of the test classes used, "conflicting" in the sense that two nested test classes may have identical display names that do not represent the runtime structure of the test classes.

## Example

```java
@IndicativeSentencesGeneration
abstract class AbstractBaseTests {

	@Nested
	class NestedTests {

		@Test
		void test() {
		}
	}
}
```

```java
class ScenarioOneTests extends AbstractBaseTests {
}
```

```java
class ScenarioTwoTests extends AbstractBaseTests {
}
```

## Actual Behavior

When running `ScenarioOneTests` and `ScenarioTwoTests`, we currently see the following display names.

- `ScenarioOneTests`
  - `AbstractBaseTests, NestedTests`
    - `AbstractBaseTests, NestedTests, test()`
- `ScenarioTwoTests`
  - `AbstractBaseTests, NestedTests`
    - `AbstractBaseTests, NestedTests, test()`

## Expected Behavior

When running `ScenarioOneTests` and `ScenarioTwoTests`, we would expect the following display names.

- `ScenarioOneTests`
  - `ScenarioOneTests, NestedTests`
    - `ScenarioOneTests, NestedTests, test()`
- `ScenarioTwoTests`
  - `ScenarioTwoTests, NestedTests`
    - `ScenarioTwoTests, NestedTests, test()`

## Related Issues

- #4131

## Deliverables

- [x] Ensure that a `DisplayNameGenerator` can access the concrete runtime type of the enclosing instance for a `@Nested` test class.

Team decision: Change DisplayNameGenerator as follows:

@Deprecated
default String generateDisplayNameForNestedClass(Class<?> nestedClass) {
	throw new UnsupportedOperationException("Implement generateDisplayNameForNestedClass(List<Class<?>>, Class<?>) instead")
}

default String generateDisplayNameForNestedClass(List<Class<?>> enclosingInstanceTypes, Class<?> nestedClass) {
	return generateDisplayNameForNestedClass(nestedClass);
}

@Deprecated
default String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
	throw new UnsupportedOperationException("Implement generateDisplayNameForMethod(List<Class<?>>, Class<?>, Method) instead");
}

default String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
		Method testMethod) {
	return generateDisplayNameForMethod(testClass, testMethod);
}
In addition, the actual instance types need to be collected via the parent TestDescriptors and the new methods should be called instead of the old ones.

Resolve the issue described above. Do not modify tests or user code. The issue can be resolved by modifying the actual library/application code. Make the minimal changes to resolve the issue while adhering to the specification and coding style of the codebase.

diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java
index 810d180e3da8..89f7784d8c57 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java
@@ -280,10 +359,16 @@ private String getSentenceBeginning(Class<?> testClass) {
 					.filter(IndicativeSentences.class::equals)//
 					.isPresent();

-			String prefix = (buildPrefix ? getSentenceBeginning(enclosingClass) + getFragmentSeparator(testClass) : "");
+			List<Class<?>> remainingEnclosingInstanceTypes = enclosingInstanceTypes.isEmpty() ? emptyList()
+					: enclosingInstanceTypes.subList(0, enclosingInstanceTypes.size() - 1);
+
+			String prefix = (buildPrefix
+					? getSentenceBeginning(remainingEnclosingInstanceTypes, enclosingClass)
+							+ getFragmentSeparator(testClass)
+					: "");

-			return prefix + displayName.orElseGet(
-				() -> getGeneratorFor(testClass).generateDisplayNameForNestedClass(testClass));
+			return prefix + displayName.orElseGet(() -> getGeneratorFor(testClass).generateDisplayNameForNestedClass(
+				remainingEnclosingInstanceTypes, testClass));
 		}

 		/**

diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java
index 8dbea0cdb42d..c7a1fe90efc6 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java
@@ -20,6 +20,7 @@

 import java.lang.reflect.Method;
 import java.util.EmptyStackException;
+import java.util.List;
 import java.util.Stack;

 import org.junit.jupiter.engine.AbstractJupiterTestEngineTests;
@@ -197,6 +198,23 @@ void indicativeSentencesGenerationInheritance() {
 		);
 	}

+	@Test
+	void indicativeSentencesRuntimeEnclosingType() {
+		check(IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.class, //
+			"CONTAINER: Scenario 1", //
+			"CONTAINER: Scenario 1 -> Level 1", //
+			"CONTAINER: Scenario 1 -> Level 1 -> Level 2", //
+			"TEST: Scenario 1 -> Level 1 -> Level 2 -> this is a test"//
+		);
+
+		check(IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.class, //
+			"CONTAINER: Scenario 2", //
+			"CONTAINER: Scenario 2 -> Level 1", //
+			"CONTAINER: Scenario 2 -> Level 1 -> Level 2", //
+			"TEST: Scenario 2 -> Level 1 -> Level 2 -> this is a test"//
+		);
+	}
+
 	private void check(Class<?> testClass, String... expectedDisplayNames) {
 		var request = request().selectors(selectClass(testClass)).build();
 		var descriptors = discoverTests(request).getDescendants();
@@ -217,12 +235,13 @@ public String generateDisplayNameForClass(Class<?> testClass) {
 		}

 		@Override
-		public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
+		public String generateDisplayNameForNestedClass(List<Class<?>> enclosingInstanceTypes, Class<?> nestedClass) {
 			return "nn";
 		}

 		@Override
-		public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
+		public String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
+				Method testMethod) {
 			return "nn";
 		}
 	}
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.java
new file mode 100644
index 000000000000..927903c14612
--- /dev/null
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2015-2025 the original author or authors.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package org.junit.jupiter.api;
+
+/**
+ * @since 5.12
+ */
+@DisplayName("Scenario 1")
+class IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase
+		extends IndicativeSentencesRuntimeEnclosingTypeTestCase {
+}
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.java
new file mode 100644
index 000000000000..c6e2f377db10
--- /dev/null
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2015-2025 the original author or authors.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package org.junit.jupiter.api;
+
+/**
+ * @since 5.12
+ */
+@DisplayName("Scenario 2")
+class IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase
+		extends IndicativeSentencesRuntimeEnclosingTypeTestCase {
+}
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeTestCase.java
new file mode 100644
index 000000000000..86244f4cbfcc
--- /dev/null
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeTestCase.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015-2025 the original author or authors.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package org.junit.jupiter.api;
+
+import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores;
+
+/**
+ * @since 5.12
+ */
+@DisplayName("Base Scenario")
+@IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class)
+abstract class IndicativeSentencesRuntimeEnclosingTypeTestCase {
+
+	@Nested
+	class Level_1 {
+
+		@Nested
+		class Level_2 {
+
+			@Test
+			void this_is_a_test() {
+			}
+		}
+	}
+}
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java
index 2025c6e0ad49..b095a8d28e7c 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java
@@ -20,6 +20,7 @@
 import static org.mockito.Mockito.when;

 import java.lang.reflect.Method;
+import java.util.List;
 import java.util.Set;
 import java.util.stream.Stream;

@@ -255,7 +256,7 @@ private ClassTestDescriptor getClassTestDescriptor(Class<?> testClass) {

 	private Set<ExclusiveResource> getMethodResources(Class<?> testClass) {
 		var descriptor = new TestMethodTestDescriptor( //
-			uniqueId, testClass, getDeclaredTestMethod(testClass), configuration //
+			uniqueId, testClass, getDeclaredTestMethod(testClass), List::of, configuration //
 		);
 		descriptor.setParent(getClassTestDescriptor(testClass));
 		return descriptor.getExclusiveResources();
@@ -271,7 +272,7 @@ private static Method getDeclaredTestMethod(Class<?> testClass) {
 	}

 	private Set<ExclusiveResource> getNestedClassResources(Class<?> testClass) {
-		var descriptor = new NestedClassTestDescriptor(uniqueId, testClass, configuration);
+		var descriptor = new NestedClassTestDescriptor(uniqueId, testClass, List::of, configuration);
 		descriptor.setParent(getClassTestDescriptor(testClass.getEnclosingClass()));
 		return descriptor.getExclusiveResources();
 	}
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java
index f7b8afae102c..6b00ad5b9282 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java
@@ -11,6 +11,7 @@
 package org.junit.jupiter.engine.descriptor;

 import java.lang.reflect.Method;
+import java.util.List;

 import org.junit.jupiter.api.DisplayNameGenerator;

@@ -22,12 +23,13 @@ public String generateDisplayNameForClass(Class<?> testClass) {
 	}

 	@Override
-	public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
+	public String generateDisplayNameForNestedClass(List<Class<?>> enclosingInstanceTypes, Class<?> nestedClass) {
 		return "nested-class-display-name";
 	}

 	@Override
-	public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
+	public String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
+			Method testMethod) {
 		return "method-display-name";
 	}
 }
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java
index a5d019e1f95a..7a9ca9a4a165 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java
@@ -15,6 +15,7 @@
 import static org.mockito.Mockito.when;

 import java.lang.reflect.Method;
+import java.util.List;
 import java.util.function.Supplier;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
@@ -70,7 +71,7 @@ void shouldGetDisplayNameFromSupplierIfNoDisplayNameAnnotationPresent() {
 		@Nested
 		class ClassDisplayNameSupplierTests {

-			private JupiterConfiguration configuration = mock();
+			private final JupiterConfiguration configuration = mock();

 			@Test
 			void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() {
@@ -115,12 +116,12 @@ void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() {
 	@Nested
 	class NestedClassDisplayNameTests {

-		private JupiterConfiguration configuration = mock();
+		private final JupiterConfiguration configuration = mock();

 		@Test
 		void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() {
 			when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator());
-			Supplier<String> displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(
+			Supplier<String> displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(List::of,
 				StandardDisplayNameTestCase.class, configuration);

 			assertThat(displayName.get()).isEqualTo(StandardDisplayNameTestCase.class.getSimpleName());
@@ -129,7 +130,7 @@ void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() {
 		@Test
 		void shouldGetDisplayNameFromDefaultDisplayNameGenerator() {
 			when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator());
-			Supplier<String> displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(
+			Supplier<String> displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(List::of,
 				NestedTestCase.class, configuration);

 			assertThat(displayName.get()).isEqualTo("nested-class-display-name");
@@ -138,7 +139,7 @@ void shouldGetDisplayNameFromDefaultDisplayNameGenerator() {
 		@Test
 		void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() {
 			when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator());
-			Supplier<String> displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(
+			Supplier<String> displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(List::of,
 				NullDisplayNameTestCase.NestedTestCase.class, configuration);

 			assertThat(displayName.get()).isEqualTo("nested-class-display-name");
@@ -148,14 +149,14 @@ void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() {
 	@Nested
 	class MethodDisplayNameTests {

-		private JupiterConfiguration configuration = mock();
+		private final JupiterConfiguration configuration = mock();

 		@Test
 		void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() throws Exception {
 			when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator());
 			Method method = MyTestCase.class.getDeclaredMethod("test1");
-			String displayName = DisplayNameUtils.determineDisplayNameForMethod(StandardDisplayNameTestCase.class,
-				method, configuration);
+			String displayName = DisplayNameUtils.determineDisplayNameForMethod(List::of,
+				StandardDisplayNameTestCase.class, method, configuration);

 			assertThat(displayName).isEqualTo("test1()");
 		}
@@ -165,8 +166,8 @@ void shouldGetDisplayNameFromDefaultNameGenerator() throws Exception {
 			Method method = MyTestCase.class.getDeclaredMethod("test1");
 			when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator());

-			String displayName = DisplayNameUtils.determineDisplayNameForMethod(NotDisplayNameTestCase.class, method,
-				configuration);
+			String displayName = DisplayNameUtils.determineDisplayNameForMethod(List::of, NotDisplayNameTestCase.class,
+				method, configuration);

 			assertThat(displayName).isEqualTo("method-display-name");
 		}
@@ -176,8 +177,8 @@ void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() throws Exc
 			Method method = NullDisplayNameTestCase.class.getDeclaredMethod("test");
 			when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator());

-			String displayName = DisplayNameUtils.determineDisplayNameForMethod(NullDisplayNameTestCase.class, method,
-				configuration);
+			String displayName = DisplayNameUtils.determineDisplayNameForMethod(List::of, NullDisplayNameTestCase.class,
+				method, configuration);

 			assertThat(displayName).isEqualTo("method-display-name");
 		}
@@ -238,12 +239,13 @@ public String generateDisplayNameForClass(Class<?> testClass) {
 		}

 		@Override
-		public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
+		public String generateDisplayNameForNestedClass(List<Class<?>> enclosingInstanceTypes, Class<?> nestedClass) {
 			return null;
 		}

 		@Override
-		public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
+		public String generateDisplayNameForMethod(List<Class<?>> enclosingInstanceTypes, Class<?> testClass,
+				Method testMethod) {
 			return null;
 		}

diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java
index 482290a3c559..66796f302620 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java
@@ -418,7 +418,7 @@ void configurationParameter(Function<JupiterConfiguration, ? extends ExtensionCo
 			named("method", (JupiterConfiguration configuration) -> {
 				var method = ReflectionSupport.findMethod(testClass, "extensionContextFactories").orElseThrow();
 				var methodUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]");
-				var methodTestDescriptor = new TestMethodTestDescriptor(methodUniqueId, testClass, method,
+				var methodTestDescriptor = new TestMethodTestDescriptor(methodUniqueId, testClass, method, List::of,
 					configuration);
 				return new MethodExtensionContext(null, null, methodTestDescriptor, configuration, extensionRegistry,
 					null);
@@ -428,7 +428,7 @@ void configurationParameter(Function<JupiterConfiguration, ? extends ExtensionCo

 	private NestedClassTestDescriptor nestedClassDescriptor() {
 		return new NestedClassTestDescriptor(UniqueId.root("nested-class", "NestedClass"), OuterClass.NestedClass.class,
-			configuration);
+			List::of, configuration);
 	}

 	private ClassTestDescriptor outerClassDescriptor(TestDescriptor child) {
@@ -443,7 +443,7 @@ private ClassTestDescriptor outerClassDescriptor(TestDescriptor child) {
 	private TestMethodTestDescriptor methodDescriptor() {
 		try {
 			return new TestMethodTestDescriptor(UniqueId.root("method", "aMethod"), OuterClass.class,
-				OuterClass.class.getDeclaredMethod("aMethod"), configuration);
+				OuterClass.class.getDeclaredMethod("aMethod"), List::of, configuration);
 		}
 		catch (NoSuchMethodException e) {
 			throw new RuntimeException(e);
diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java
index 7a8266f7f19f..053d03498d22 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java
@@ -113,7 +113,7 @@ void constructFromClassWithInvalidAfterEachDeclaration() {
 	void constructFromMethod() throws Exception {
 		Class<?> testClass = TestCase.class;
 		Method testMethod = testClass.getDeclaredMethod("test");
-		TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, testClass, testMethod,
+		TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, testClass, testMethod, List::of,
 			configuration);

 		assertEquals(uniqueId, descriptor.getUniqueId());
@@ -127,7 +127,7 @@ void constructFromMethodWithAnnotations() throws Exception {
 		JupiterTestDescriptor classDescriptor = new ClassTestDescriptor(uniqueId, TestCase.class, configuration);
 		Method testMethod = TestCase.class.getDeclaredMethod("foo");
 		TestMethodTestDescriptor methodDescriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod,
-			configuration);
+			List::of, configuration);
 		classDescriptor.addChild(methodDescriptor);

 		assertEquals(testMethod, methodDescriptor.getTestMethod());
@@ -143,7 +143,7 @@ void constructFromMethodWithAnnotations() throws Exception {
 	void constructFromMethodWithCustomTestAnnotation() throws Exception {
 		Method testMethod = TestCase.class.getDeclaredMethod("customTestAnnotation");
 		TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod,
-			configuration);
+			List::of, configuration);

 		assertEquals(testMethod, descriptor.getTestMethod());
 		assertEquals("custom name", descriptor.getDisplayName(), "display name:");
@@ -155,7 +155,7 @@ void constructFromMethodWithCustomTestAnnotation() throws Exception {
 	void constructFromMethodWithParameters() throws Exception {
 		Method testMethod = TestCase.class.getDeclaredMethod("test", String.class, BigDecimal.class);
 		TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod,
-			configuration);
+			List::of, configuration);

 		assertEquals(testMethod, descriptor.getTestMethod());
 		assertEquals("test(String, BigDecimal)", descriptor.getDisplayName(), "display name");
@@ -166,7 +166,7 @@ void constructFromMethodWithParameters() throws Exception {
 	void constructFromMethodWithPrimitiveArrayParameter() throws Exception {
 		Method testMethod = TestCase.class.getDeclaredMethod("test", int[].class);
 		TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod,
-			configuration);
+			List::of, configuration);

 		assertEquals(testMethod, descriptor.getTestMethod());
 		assertEquals("test(int[])", descriptor.getDisplayName(), "display name");
@@ -177,7 +177,7 @@ void constructFromMethodWithPrimitiveArrayParameter() throws Exception {
 	void constructFromMethodWithObjectArrayParameter() throws Exception {
 		Method testMethod = TestCase.class.getDeclaredMethod("test", String[].class);
 		TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod,
-			configuration);
+			List::of, configuration);

 		assertEquals(testMethod, descriptor.getTestMethod());
 		assertEquals("test(String[])", descriptor.getDisplayName(), "display name");
@@ -188,7 +188,7 @@ void constructFromMethodWithObjectArrayParameter() throws Exception {
 	void constructFromMethodWithMultidimensionalPrimitiveArrayParameter() throws Exception {
 		Method testMethod = TestCase.class.getDeclaredMethod("test", int[][][][][].class);
 		TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod,
-			configuration);
+			List::of, configuration);

 		assertEquals(testMethod, descriptor.getTestMethod());
 		assertEquals("test(int[][][][][])", descriptor.getDisplayName(), "display name");
@@ -199,7 +199,7 @@ void constructFromMethodWithMultidimensionalPrimitiveArrayParameter() throws Exc
 	void constructFromMethodWithMultidimensionalObjectArrayParameter() throws Exception {
 		Method testMethod = TestCase.class.getDeclaredMethod("test", String[][][][][].class);
 		TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod,
-			configuration);
+			List::of, configuration);

 		assertEquals(testMethod, descriptor.getTestMethod());
 		assertEquals("test(String[][][][][])", descriptor.getDisplayName(), "display name");
@@ -210,7 +210,7 @@ void constructFromMethodWithMultidimensionalObjectArrayParameter() throws Except
 	void constructFromInheritedMethod() throws Exception {
 		Method testMethod = ConcreteTestCase.class.getMethod("theTest");
 		TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, ConcreteTestCase.class, testMethod,
-			configuration);
+			List::of, configuration);

 		assertEquals(testMethod, descriptor.getTestMethod());

@@ -230,7 +230,7 @@ void shouldTakeCustomMethodNameDescriptorFromConfigurationIfPresent() {
 		assertEquals("class-display-name", descriptor.getDisplayName());
 		assertEquals(getClass().getName(), descriptor.getLegacyReportingName());

-		descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, configuration);
+		descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, List::of, configuration);
 		assertEquals("nested-class-display-name", descriptor.getDisplayName());
 		assertEquals(NestedTestCase.class.getName(), descriptor.getLegacyReportingName());

@@ -249,7 +249,7 @@ void defaultDisplayNamesForTestClasses() {
 		assertEquals(getClass().getSimpleName(), descriptor.getDisplayName());
 		assertEquals(getClass().getName(), descriptor.getLegacyReportingName());

-		descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, configuration);
+		descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, List::of, configuration);
 		assertEquals(NestedTestCase.class.getSimpleName(), descriptor.getDisplayName());
 		assertEquals(NestedTestCase.class.getName(), descriptor.getLegacyReportingName());

@@ -269,7 +269,7 @@ void enclosingClassesAreDerivedFromParent() {
 		ClassBasedTestDescriptor parentDescriptor = new ClassTestDescriptor(uniqueId, StaticTestCase.class,
 			configuration);
 		ClassBasedTestDescriptor nestedDescriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class,
-			configuration);
+			List::of, configuration);
 		assertThat(parentDescriptor.getEnclosingTestClasses()).isEmpty();
 		assertThat(nestedDescriptor.getEnclosingTestClasses()).isEmpty();

diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java
index 618d290870d2..6ad584b728df 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java
@@ -18,6 +18,7 @@
 import java.io.File;
 import java.lang.reflect.Method;
 import java.net.URI;
+import java.util.List;
 import java.util.Optional;
 import java.util.stream.Stream;

@@ -147,7 +148,7 @@ void before() throws Exception {

 			Method testMethod = CustomStreamTestCase.class.getDeclaredMethod("customStream");
 			descriptor = new TestFactoryTestDescriptor(UniqueId.forEngine("engine"), CustomStreamTestCase.class,
-				testMethod, jupiterConfiguration);
+				testMethod, List::of, jupiterConfiguration);
 			when(extensionContext.getTestMethod()).thenReturn(Optional.of(testMethod));
 		}

diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java
index 0491150abb57..65b2bf268688 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java
@@ -16,6 +16,7 @@
 import static org.mockito.Mockito.when;

 import java.lang.reflect.Method;
+import java.util.List;

 import org.junit.jupiter.api.DisplayNameGenerator;
 import org.junit.jupiter.api.Test;
@@ -34,7 +35,7 @@ void invocationsDoNotDeclareExclusiveResources() throws Exception {
 		JupiterConfiguration configuration = mock();
 		when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard());
 		TestTemplateTestDescriptor parent = new TestTemplateTestDescriptor(UniqueId.root("segment", "template"),
-			testClass, testTemplateMethod, configuration);
+			testClass, testTemplateMethod, List::of, configuration);
 		TestTemplateInvocationContext invocationContext = mock();
 		when(invocationContext.getDisplayName(anyInt())).thenReturn("invocation");

diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java
index 1cd492db23ce..c0dab4d6a66e 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java
@@ -15,6 +15,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;

+import java.util.List;
 import java.util.Set;

 import org.junit.jupiter.api.DisplayNameGenerator;
@@ -45,7 +46,7 @@ void inheritsTagsFromParent() throws Exception {

 		TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor(
 			parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class,
-			MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration);
+			MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration);
 		parent.addChild(testDescriptor);

 		assertThat(testDescriptor.getTags()).containsExactlyInAnyOrder(TestTag.create("foo"), TestTag.create("bar"),
@@ -63,7 +64,7 @@ void shouldUseCustomDisplayNameGeneratorIfPresentFromConfiguration() throws Exce

 		TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor(
 			parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class,
-			MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration);
+			MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration);
 		parent.addChild(testDescriptor);

 		assertThat(testDescriptor.getDisplayName()).isEqualTo("method-display-name");
@@ -80,7 +81,7 @@ void shouldUseStandardDisplayNameGeneratorIfConfigurationNotPresent() throws Exc

 		TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor(
 			parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class,
-			MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration);
+			MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration);
 		parent.addChild(testDescriptor);

 		assertThat(testDescriptor.getDisplayName()).isEqualTo("testTemplate()");


diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java
index 652c606d63cb..3607def6d0aa 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java
@@ -33,7 +33,7 @@
@

diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.3.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.3.adoc
index 55fcc69e76fd..d364b73b4739 100644
--- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.3.adoc
+++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.3.adoc
@@ -38,10 +38,11 @@ on GitHub.

 * Extensions can once again be registered via multiple `@ExtendWith` meta-annotations on
   the same composed annotation on a field within a test class.
+* `@ExtendWith` annotations can now also be repeated when used directly on fields and
+  parameters.
 * All `@...Source` annotations of parameterized tests can now also be repeated when used
   as meta annotations.

-
 [[release-notes-5.11.3-junit-jupiter-deprecations-and-breaking-changes]]
 ==== Deprecations and Breaking Changes

diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java
index 652c606d63cb..3607def6d0aa 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java
@@ -33,7 +33,7 @@
  * @see ExtendWith
  * @see java.lang.annotation.Repeatable
  */
-@Target({ ElementType.TYPE, ElementType.METHOD })
+@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Inherited

Make Extensions annotation applicable to fields and parameters in addition to types and methods.

Prompt:
The following is the text of a github issue and related comments for this repository:

`@ExtendWith` cannot be used as a repeatable annotation directly on fields and parameters
## Overview

When I introduced support for declarative extension registration on fields and parameters in JUnit Jupiter 5.8, I neglected to sync the `@Target` declaration for `@Extensions` with the new supported targets for `@ExtendWith`.

Consequently, it is possible to declare a single `@ExtendWith` annotation on a field or parameter, but it is not currently possible to declare multiple `@ExtendWith` annotations directly on a field or parameter.

For example, the following currently fails to compile.

```java
@ExtendWith(RandomNumberExtension.class)
@ExtendWith(NumberParameterResolver.class)
private int magicNumber;
```

Similarly, the following also fails to compile.

```java
@BeforeEach
void prepareTestData(
		@ExtendWith(RandomNumberExtension.class)
		@ExtendWith(NumberParameterResolver.class)
		int magicNumber
	) {

	// method body...
}
```

## Related Issues

- #864

## Deliverables

- [x] Ensure `@ExtendWith` can be used as a repeatable annotation directly on fields and parameters
- [x] Remove exclusion in `ArchUnitTests`
- [x] Backport to 5.11.x branch

Resolve the issue described above. Do not modify tests or user code. The issue can be resolved by modifying the actual library/application code. Make the minimal changes to resolve the issue while adhering to the specification and coding style of the codebase.

diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java
index 9e934c3a9875..a9f04addddd6 100644
--- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java
+++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java
@@ -132,6 +132,12 @@ void testTemplateMethodParameter() {
 		assertTestsSucceeded(TestTemplateMethodParameterTestCase.class, 2);
 	}

+	@Test
+	void multipleRegistrationsViaParameter(@TrackLogRecords LogRecordListener listener) {
+		assertOneTestSucceeded(MultipleRegistrationsViaParameterTestCase.class);
+		assertThat(getRegisteredLocalExtensions(listener)).containsExactly("LongParameterResolver", "DummyExtension");
+	}
+
 	@Test
 	void staticField() {
 		assertOneTestSucceeded(StaticFieldTestCase.class);
@@ -147,9 +153,11 @@ void fieldsWithTestInstancePerClass() {
 		assertOneTestSucceeded(TestInstancePerClassFieldTestCase.class);
 	}

-	@Test
-	void multipleRegistrationsViaField(@TrackLogRecords LogRecordListener listener) {
-		assertOneTestSucceeded(MultipleRegistrationsViaFieldTestCase.class);
+	@ParameterizedTest
+	@ValueSource(classes = { MultipleMixedRegistrationsViaFieldTestCase.class,
+			MultipleExtendWithRegistrationsViaFieldTestCase.class })
+	void multipleRegistrationsViaField(Class<?> testClass, @TrackLogRecords LogRecordListener listener) {
+		assertOneTestSucceeded(testClass);
 		assertThat(getRegisteredLocalExtensions(listener)).containsExactly("LongParameterResolver", "DummyExtension");
 	}

@@ -567,14 +575,37 @@ private static TestTemplateInvocationContext emptyTestTemplateInvocationContext(
 		}
 	}

-	static class MultipleRegistrationsViaFieldTestCase {
+	@ExtendWith(LongParameterResolver.class)
+	static class MultipleRegistrationsViaParameterTestCase {
+
+		@Test
+		void test(@ExtendWith(DummyExtension.class) @ExtendWith(LongParameterResolver.class) Long number) {
+			assertThat(number).isEqualTo(42L);
+		}
+	}
+
+	static class MultipleMixedRegistrationsViaFieldTestCase {

 		@ExtendWith(LongParameterResolver.class)
 		@RegisterExtension
 		DummyExtension dummy = new DummyExtension();

 		@Test
-		void test() {
+		void test(Long number) {
+			assertThat(number).isEqualTo(42L);
+		}
+	}
+
+	static class MultipleExtendWithRegistrationsViaFieldTestCase {
+
+		@SuppressWarnings("unused")
+		@ExtendWith(LongParameterResolver.class)
+		@ExtendWith(DummyExtension.class)
+		Object field;
+
+		@Test
+		void test(Long number) {
+			assertThat(number).isEqualTo(42L);
 		}
 	}

diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java
index 3091293004dd..1ecbc4764582 100644
--- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java
+++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java
@@ -17,7 +17,6 @@
 import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage;
 import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAnyPackage;
 import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName;
-import static com.tngtech.archunit.core.domain.JavaClass.Predicates.type;
 import static com.tngtech.archunit.core.domain.JavaModifier.PUBLIC;
 import static com.tngtech.archunit.core.domain.properties.HasModifiers.Predicates.modifier;
 import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name;
@@ -51,7 +50,6 @@
 import com.tngtech.archunit.library.GeneralCodingRules;

 import org.apiguardian.api.API;
-import org.junit.jupiter.api.extension.ExtendWith;

 @AnalyzeClasses(locations = ArchUnitTests.AllJars.class)
 class ArchUnitTests {
@@ -72,7 +70,6 @@ class ArchUnitTests {
 			.that(nameStartingWith("org.junit.")) //
 			.and().areAnnotations() //
 			.and().areAnnotatedWith(Repeatable.class) //
-			.and(are(not(type(ExtendWith.class)))) // to be resolved in https://github.com/junit-team/junit5/issues/4059
 			.should(haveContainerAnnotationWithSameRetentionPolicy()) //
 			.andShould(haveContainerAnnotationWithSameTargetTypes());