A JUnit 5 run fails before any test body executes, with a ParameterResolutionException. What are the distinct situations that produce that exception, and how do you diagnose and fix each one?
answer
- four shapes: none / competing / wrong type / resolver threw
- 'No ParameterResolver registered for parameter [...]' → missing @ExtendWith or RUNTIME retention
- 'multiple competing ParameterResolvers' → narrow claim to marker annotation
- null for a primitive is an error; wrong type is an error
- parameterized args own the leading parameter indexes
basics
~20 sFour causes: no registered resolver supports a declared parameter; two resolvers both claim it (competing resolvers); the resolver returned a value incompatible with the declared type (or null for a primitive); or supportsParameter/resolveParameter itself threw. Fix by registering the extension, narrowing claims with a marker annotation, or correcting the returned type.
solid answer
~50 s`ParameterResolutionException` means the engine could not supply an argument, so the executable never ran. The message names the parameter and the declaring executable — read it first. 1. **No resolver registered** — *No ParameterResolver registered for parameter [X] in method [...]*. Usually a missing `@ExtendWith`, a marker annotation without `RUNTIME` retention, or a type the resolver's check rejects. 2. **Competing resolvers** — two registered resolvers both returned `true` for the same parameter. The engine refuses to guess. Fix by narrowing one claim, typically to a marker annotation plus the exact type, or by not registering both. 3. **Incompatible returned value** — `resolveParameter` returned something not assignable to the declared type, or `null` for a primitive. 4. **The resolver threw** — an exception out of `supportsParameter` or `resolveParameter` is wrapped in this type; the cause has the real story. A fifth, subtler shape: a resolver claiming a parameter index that a parameterized test's own argument resolver owns.
code
java · 9 lines// collides with any other extension that resolves String parameters
public boolean supportsParameter(ParameterContext pc, ExtensionContext ec) {
return pc.getParameter().getType() == String.class;
}
// narrowed: annotation-gated, unambiguous at the injection point
public boolean supportsParameter(ParameterContext pc, ExtensionContext ec) {
return pc.isAnnotated(TenantId.class) && pc.getParameter().getType() == String.class;
}go deeper
Recognise the exception as a wiring problem, and know the most common cause: the extension was not registered.
Enumerate the shapes — none, competing, incompatible value, resolver threw — and map each to a fix.
Add the diagnostic routine: read the message and the cause chain, enumerate all extensions in scope including inherited and ServiceLoader ones, and understand the parameterized-argument index collision.
Prevent it by convention: one responsibility per resolver, claims gated by marker annotations, extensions unit-tested, and a documented registry so teams do not register colliding resolvers across a shared base class.
## What the exception means `org.junit.jupiter.api.extension.ParameterResolutionException` is thrown by the engine when it cannot produce an argument for a declared parameter of an executable it is about to invoke — a test constructor, a lifecycle method, or a test method. It is a **configuration/wiring failure**, not a test assertion failure, and it happens *before* the body runs, so no production code was exercised. The message always names the offending parameter and its declaring executable; that is nearly always enough to diagnose it. ## Cause 1 — nothing supports the parameter Message shape: *No ParameterResolver registered for parameter [com.example.Client client] in method [...]*. Every declared parameter must be claimed by exactly one resolver, and no resolver claimed this one. Common reasons: - The extension was never registered — missing `@ExtendWith` on the class, or it was placed on a different class than you think. - The marker annotation lacks `@Retention(RetentionPolicy.RUNTIME)`, so `parameterContext.isAnnotated(...)` never sees it. - The type check is too strict: `pc.getParameter().getType() == Impl.class` fails when the parameter is declared as the interface. Use `isAssignableFrom` against the declared type when you intend to support subtypes. - A built-in type used out of context — for example `RepetitionInfo` on a plain `@Test`, where the built-in resolver deliberately does not support it. ## Cause 2 — competing resolvers Message shape: *Discovered multiple competing ParameterResolvers for parameter [...]*, followed by the resolver class names. Two (or more) registered resolvers returned `true` for the same parameter. The engine will not pick arbitrarily, because a silent pick is a source of extremely confusing test behaviour. This usually happens when a resolver claims a broad type — `String`, `Path`, `Object`, a widely used domain interface — and another extension (yours, a framework's, or a class-level one inherited from a base class) claims it too. Registration is cumulative: class-level, method-level, interface-level, `@RegisterExtension` fields and `ServiceLoader` registrations all add up, so a resolver you did not put on this class may still be in play. The durable fix is to narrow the claim: require a marker annotation on the parameter *and* check the type. "Claim by annotation" makes the intent explicit at the injection point and makes accidental overlap nearly impossible. The blunt fix is to stop registering one of the two for that scope. ## Cause 3 — the resolver returned the wrong thing `resolveParameter` returns `Object`, so the compiler cannot help. The engine checks the returned value against the declared parameter type and throws if it is not assignable — for example a resolver that supports a `Duration` parameter but returns a `Long`. Returning `null` for a **primitive** parameter is also an error. Returning `null` for a reference type is permitted but is usually a design bug: the test then NPEs somewhere far from the cause. This cause is common in resolvers whose `supportsParameter` is broader than their `resolveParameter` — they claim a family of types but only know how to build one of them. Keep the two in lockstep: whatever `supportsParameter` claims, `resolveParameter` must be able to build. ## Cause 4 — the resolver itself blew up An exception thrown from either method is wrapped in a `ParameterResolutionException` whose **cause** carries the real failure — a connection refused while eagerly building a client, an `IllegalStateException` from a misconfigured factory. Always read the cause chain, not just the top frame. Corollary discipline: `supportsParameter` should be a pure, cheap predicate, and expensive or fallible construction belongs in `resolveParameter` where it is at least attributable to the parameter you claimed. ## Cause 5 — colliding with parameterized-test arguments A parameterized test's arguments are themselves injected through a resolver registered for each invocation, and it claims the **leading parameters** — index 0 up to the number of arguments in the current argument set. Two failure shapes follow. If a custom resolver also claims one of those indexes by type, you get competing resolvers. If you declare more leading parameters than the argument set supplies, nothing claims the extras and you get "no resolver registered". The convention that avoids both: argument parameters first, injected extras (`TestInfo`, `TestReporter`, your own annotated types) after them. ## A diagnostic routine 1. Read the message: which parameter, which executable, and which of the four shapes is it. 2. If "no resolver": check registration, retention of the marker annotation, and the type predicate. 3. If "competing": list every extension in scope for that class, including inherited class-level registrations, `ServiceLoader` entries and framework extensions, then narrow one claim to an annotation. 4. If "incompatible value": align `resolveParameter`'s return with the declared type, including boxing and primitives. 5. If wrapped: read the cause. 6. Reproduce with a single-method run to remove noise; the exception is deterministic, so a minimal test class settles it quickly. ## Prevention One responsibility per resolver, claims scoped by a marker annotation, `supportsParameter` pure and total, `resolveParameter` never returning a type it did not promise, and a short unit test for the extension itself — resolvers are ordinary classes and can be tested directly by handing them a stub `ParameterContext`.
- Two extensions both claim a `String` parameter and you own only one of them. What is the cleanest fix?Narrow your own resolver so it requires a marker annotation on the parameter in addition to the `String` type — `parameterContext.isAnnotated(MyValue.class)` — and annotate the injection points. That removes the overlap without touching the third-party extension and makes the intent visible in the test signature. Removing your registration from that scope is the fallback if you cannot change the call sites.
- Why does the engine refuse to choose between two resolvers instead of using registration order?Because a silent choice is unreliable and invisible: which extension wins would depend on registration order across class-level, method-level, inherited and ServiceLoader registrations, so the same test could get a different object in a different context. Failing loudly turns a subtle wrong-object bug into a wiring error you fix once.
- How would you unit-test a ParameterResolver you wrote?Treat it as a plain class: construct it and call `supportsParameter` with a `ParameterContext` whose parameter comes from reflection on a sample method, asserting true for annotated parameters of the right type and false otherwise; then call `resolveParameter` and assert the returned instance's type and configuration. That catches the over-broad claim and the wrong-return-type bugs before they surface as run-wide failures.
saying these in an interview costs you the question
- Treating ParameterResolutionException as a test failure in production code rather than a wiring error
- Assuming the last registered resolver wins when two compete
- Forgetting `@Retention(RUNTIME)` on a marker annotation and blaming the engine
- Returning null from resolveParameter for a primitive parameter
- Declaring injected parameters before the parameterized-test argument parameters