junit-team/junit5 · error · JUnitException
Chain of InvocationInterceptors never called invocation
Error message
Chain of InvocationInterceptors never called invocation: %s
What it means
Internal assertion inside ValidatingInvocation: after running the entire InvocationInterceptor chain for an intercepted call (constructor/method/lifecycle), invokedOrSkipped was never set, meaning no interceptor ever called Invocation.proceed() or Invocation.skip(). The message lists every interceptor in the chain so you can see which one swallowed the call.
Solutions
- Identify the listed interceptor that owns the missing proceed() call.
- Ensure the interceptor always either calls invocation.proceed() (to continue) or invocation.skip() (to halt) on every code path.
- If the interceptor intentionally replaces the invocation, call proceed() against the wrapped chain or invoke the underlying target directly and return its value - but never silently drop.
Example fix
// before
@Override
public <T> T interceptTestClassConstructor(Invocation<T> inv, ReflectiveInvocationContext<T> ctx, ExtensionContext ec) {
log("creating");
return null; // forgot proceed()
}
// after
@Override
public <T> T interceptTestClassConstructor(Invocation<T> inv, ReflectiveInvocationContext<T> ctx, ExtensionContext ec) throws Throwable {
log("creating");
return inv.proceed();
} Defensive patterns
Strategy: validation
Prevention
- Every InvocationInterceptor.intercept* overload must call invocation.proceed() or invocation.skip() on every path.
- Audit try/catch/finally blocks in interceptors to ensure exactly one proceed() invocation.
- Write a unit test that wraps your interceptor in an InvocationInterceptorChain and asserts invocation completes.
When it happens
Trigger: An InvocationInterceptor.intercept* method returns without ever calling invocation.proceed() or invocation.skip(); verifyInvokedAtLeastOnce() then fires at the end of InvocationInterceptorChain.invoke. The throw site is at line 142-143 / fail() at 149.
Common situations: A third-party or custom InvocationInterceptor short-circuits execution intentionally but forgets to either proceed or skip; an interceptor swallows the Invocation and substitutes its own logic without delegating; an interceptor throws but the throw is caught upstream leaving the chain un-proceeded (rare).
Related errors
- Chain of InvocationInterceptors called invocation multiple…
- Can only resolve @TempDir
- Discovered multiple competing ParameterResolvers for…
- Failed to create default temp directory
- Failed to resolve parameter
AI-assisted analysis of junit-team/junit5@f070c699a0 (2026-08-11).
Data as JSON: /api/errors/21eed077f733a05e.
Report an issue: GitHub.
Appendix: source
Thrown at junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java:149
delegate.skip();
}
private void markInvokedOrSkipped() {
if (!invokedOrSkipped.compareAndSet(false, true)) {
fail("Chain of InvocationInterceptors called invocation multiple times instead of just once");
}
}
void verifyInvokedAtLeastOnce() {
if (!invokedOrSkipped.get()) {
fail("Chain of InvocationInterceptors never called invocation");
}
}
private void fail(String prefix) {
String commaSeparatedInterceptorClasses = interceptors.stream().map(Object::getClass).map(
Class::getName).collect(joining(", "));
throw new JUnitException(prefix + ": " + commaSeparatedInterceptorClasses);
}
}
}
View on GitHub (pinned to f070c699a0)