junit-team/junit5 · error · JUnitException
Detected cycle in inner class hierarchy between
Error message
Detected cycle in inner class hierarchy between %s and %s
What it means
JUnitException thrown by ReflectionUtils.detectInnerClassCycle via CycleErrorHandling.THROW_EXCEPTION when an inner class's superclass also appears in its enclosing-class chain - i.e. the inner-class hierarchy contains a cycle. Used during nested-test and @Nested discovery to prevent infinite recursion when walking enclosing classes.
Solutions
- Refactor the offending class so its enclosing chain does not include its superclass
- Use ReflectionUtils.isNestedClassPresent(..., CycleErrorHandling.ABORT_VISIT) to skip rather than throw
- Regenerate the bytecode without the cycle if a tool produced it
- File a bug against the bytecode tool if cycles appear without user action
Example fix
// before
class Outer {
class Inner extends Outer {} // cycle: Inner's superclass is its enclosing class
}
// after - option A: break the cycle
class Outer {
static class Inner {}
}
// after - option B: tolerate the cycle at scan time
ReflectionUtils.isNestedClassPresent(clazz, predicate, CycleErrorHandling.ABORT_VISIT); Defensive patterns
Strategy: fallback
Try / catch
try {
ReflectionUtils.isNestedClassPresent(clazz, predicate, CycleErrorHandling.THROW_EXCEPTION);
} catch (JUnitException e) {
// fall back to skipping the cycle rather than aborting discovery
ReflectionUtils.isNestedClassPresent(clazz, predicate, CycleErrorHandling.ABORT_VISIT);
} Prevention
- Avoid inner classes that extend their own enclosing class
- Use CycleErrorHandling.ABORT_VISIT when scanning untrusted or generated class hierarchies
- Validate generated bytecode for inner-class metadata cycles
When it happens
Trigger: A class A.B (inner of A) whose superclass is A itself, or any inner class whose enclosing chain loops back through its superclass. Encountered by findNestedClasses / isNestedClassPresent when called with CycleErrorHandling.THROW_EXCEPTION (the default for several discovery predicates).
Common situations: Bytecode-engineered classes (ASM, ByteBuddy, Mockito mocks) that emit inner-class metadata creating a cycle; obfuscated classes; rarely hand-written pathological hierarchies; generated test fixtures.
Related errors
- Could not find any resource(s) with name
- Could not instantiate TimeZoneProvider because of exception
- Failed to create a java.net.URI from
- Failed to find constructor for
- Failed to inject parameter value into field
AI-assisted analysis of junit-team/junit5@f070c699a0 (2026-08-11).
Data as JSON: /api/errors/af6ac804731850da.
Report an issue: GitHub.
Appendix: source
Thrown at junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java:2022
* exception}; otherwise, this method returns the supplied {@code Throwable}.
*/
static Throwable getUnderlyingCause(Throwable t) {
if (t instanceof InvocationTargetException ite) {
return getUnderlyingCause(ite.getTargetException());
}
return t;
}
/**
* @since 1.13.2
*/
@API(status = INTERNAL, since = "1.13.2")
public enum CycleErrorHandling {
THROW_EXCEPTION {
@Override
void handle(Class<?> clazz, Class<?> enclosing) {
throw new JUnitException("Detected cycle in inner class hierarchy between %s and %s".formatted(
clazz.getName(), enclosing.getName()));
}
},
ABORT_VISIT {
@Override
void handle(Class<?> clazz, Class<?> enclosing) {
// ignore
}
};
abstract void handle(Class<?> clazz, Class<?> enclosing);
}
}
View on GitHub (pinned to f070c699a0)