skip to content

A JUnit 5 parameterized test fails with messages like "Cannot convert null to primitive value of type int" or "No built-in converter for source type java.lang.String and target type ...". How do you diagnose each, and what are the fixes?

level: seniorimportance: should knowfreq 28%

answer

  1. null → primitive is refused, never defaulted
  2. use wrapper type + explicit meaning for null
  3. "no built-in converter" = not built-in and no single-String factory
  4. interface/abstract/inner class defeats the fallback
  5. some rows pass = per-invocation, not signature

basics

~20 s

The first means a null argument reached a primitive parameter — declare it as the wrapper type (Integer) and handle null explicitly. The second means the target type is neither built-in nor has a single-String factory method or constructor — add one or annotate the parameter with @ConvertWith.

solid answer

~60 s

Both are `ArgumentConversionException` variants, and each points at a specific gap. **"Cannot convert null to primitive value of type int"** — an argument was `null` (a null-producing source, an empty/blank column mapped to null) and the parameter is a primitive. JUnit never substitutes `0`. Fix by declaring the parameter as the wrapper (`Integer`) and asserting what null means, or by ensuring the source never emits null for that column. **"No built-in converter for source type java.lang.String and target type X"** — `X` is not in the built-in table and offers no non-private single-`String` static factory method or constructor (or has several ambiguous factory methods, or is an interface/abstract type). Fix by adding a single canonical factory, or — usually better — with `@ConvertWith(XConverter.class)`. Related cases worth naming: a `DateTimeParseException` cause means the text is not ISO-8601, so use `@JavaTimeConversionPattern`; an `Enum.valueOf` failure means the text does not match the constant name exactly. Triage tip: these fail per invocation, so some rows still pass — unlike a signature error, which fails the method with zero invocations.

go deeper

for a junior

Recognise the two messages and know the basic fixes: use Integer instead of int, and give the type a single-String factory or a converter.

for a middle

Explain why null is refused for primitives, and walk the checklist for why the fallback did not find a factory.

for a senior

Triage by invocation count, cover the DateTimeParseException and Enum.valueOf variants, and refuse silent defaults as a correctness rule.

for a principal

Point at the invisible coupling between production type APIs and implicit conversion, and set conventions (canonical data formats, wrapper types for optional columns, shared converters) that keep the failure surface small.

## Reading conversion failures Parameterized-test failures split into two families, and telling them apart is the first diagnostic step: - **Method-level configuration failures** — the method reports a single error and *zero invocations*. Cause: an invalid signature, a bad source declaration, ordering rules broken. - **Per-invocation failures** — some rows pass and others fail. Conversion failures live here: they occur while binding one argument of one invocation. Everything below is the second kind, and all of it surfaces as `ArgumentConversionException` (sometimes wrapping a more specific cause). ## "Cannot convert null to primitive value of type int" JUnit's default converter passes `null` through unchanged for reference target types, but refuses it for primitives — there is no defensible default, and silently binding `0` would make tests pass against a value nobody wrote. Where the null comes from: - a source that deliberately produces null values for a column; - a `@MethodSource` returning `Arguments.of(null, …)`; - a null returned by your own `@ConvertWith` converter (a `TypedArgumentConverter` passes null through by design). Fixes, in order of preference: 1. **Declare the wrapper type** (`Integer`, `Long`, `Boolean`) and make the test say what a missing value means — usually an explicit branch or an assertion that the call rejects it. 2. **Fix the data** if null was accidental — for example a trailing comma or a blank cell that was never meant to be a case. 3. **Model absence in the domain** if "missing" is a real case worth testing; then the parameter type is a domain type and conversion is explicit. The anti-pattern is a converter that turns null into `0` so the primitive binds. That is the silent-default failure mode: the test now exercises the zero case while claiming to exercise the missing case. ## "No built-in converter for source type java.lang.String and target type X" This says the default converter exhausted both of its options: `X` is not in the built-in table (primitives, wrappers, enums, `java.time`, `File`/`Path`, `URI`/`URL`, `UUID`, `BigDecimal`/`BigInteger`, `Charset`, `Currency`, `Locale`, `Class`), and the fallback found no usable factory. Check, in this order: - Is the parameter declared as an **interface or abstract type**? The declared type is what is inspected, so an interface can never be constructed by the fallback. - Is the factory method **static and non-private**, declared **in `X` itself**, taking exactly one `String`, returning `X`? - Does `X` declare **two or more** single-`String` static factory methods with no single-`String` constructor to fall back on? Ambiguous factories are ignored. - Is `X` an **inner (non-static) class**? Then its `String` constructor cannot be used. Fixes: add one canonical single-`String` factory if that is a legitimate part of the type's API, or annotate the parameter with `@ConvertWith(XConverter.class)`. Prefer the converter when adding a `String` constructor to production code would exist purely for tests. ## Adjacent messages - **`DateTimeParseException` as the cause** — the text is not ISO-8601. Use `@JavaTimeConversionPattern("dd.MM.yyyy")` on the parameter, or a custom converter. Do not "fix" it by changing the parameter to `String` and parsing in the body; you lose the failure attribution. - **`IllegalArgumentException` from `Enum.valueOf`** — the text does not match a constant name. Enum conversion is exact and case-sensitive. Watch for stray spaces preserved by quoted CSV values. - **A non-String source that isn't assignable** — implicit conversion only handles `String` sources plus primitive widening; an object from `@MethodSource` that does not fit the parameter type fails rather than being coerced. - **`ArgumentAccessException`** — the same class of problem, but raised by a typed read on an `ArgumentsAccessor`; the message names the index and target type. ## Hardening the suite A few habits keep these failures cheap: - Prefer wrapper types for any column that can legitimately be absent, and assert the absent case explicitly. - Keep test-data formats canonical (ISO dates, exact enum names) so implicit conversion carries most of the load and converters stay rare. - Never let a converter or aggregator substitute a default on failure; conversion failures must stay failures. - When a conversion error appears after a production refactor, suspect a renamed or newly overloaded factory method — the coupling between a production type's API and implicit conversion is invisible to the compiler. - Use the invocation count as the first triage signal: zero invocations means signature/source, some-pass-some-fail means data or conversion.

  • Why does JUnit refuse to bind null to a primitive parameter instead of using the type's default value?
    Because any default would be a silent substitution: the test would run against 0 or false while the data said the value was absent, and it would pass for the wrong reason. Refusing forces the author to state what absence means — usually by declaring the wrapper type and asserting the behaviour explicitly. It is the same principle as failing rather than defaulting inside a converter.
  • A conversion error appears in a parameterized test after someone refactored a production value type, with no test change. What is the likely cause?
    The type's single-String static factory was renamed, made private, or joined by a second single-String factory, which makes the candidates ambiguous and drops implicit conversion. The compiler cannot see the dependency because it is resolved reflectively at runtime. The durable fix is an explicit @ConvertWith converter so the coupling lives in test code.

saying these in an interview costs you the question

  • Expecting a null column to bind as 0 or false for a primitive parameter
  • Fixing a conversion error by widening the parameter to String and parsing in the test body, losing failure attribution
  • Assuming a per-invocation conversion failure means the whole method signature is wrong
  • Blaming the data when the real cause is a target type with ambiguous or non-static factory methods
  • Writing a converter that returns a default on parse failure to make red tests green

context