You want a JUnit 5 parameterized test to receive your own value type directly from a CSV column of text, without writing any converter class. What must that type declare for the framework's fallback conversion to work, and what makes the fallback fail?
answer
- not in built-in table → look at target type
- static, non-private, single String, returns the type
- non-private single-String constructor; top-level or static nested
- method wins over constructor
- several factory methods = ambiguous = ignored
basics
~20 sDeclare exactly one non-private static factory method taking a single String and returning that type, or a non-private single-String constructor on a top-level/static nested class. A factory method beats a constructor; several candidate factory methods are ignored, so conversion fails.
solid answer
~60 sWhen the target type is not one of JUnit's built-in conversion targets, the default converter inspects the **target type itself** for one of two things: - a **factory method** — non-private, `static`, declared in the target type, taking a single `String` and returning an instance of that type. The name is arbitrary (`of`, `parse`, `fromString`, anything). - a **factory constructor** — a non-private constructor taking a single `String`. The type must be a top-level class or a *static* nested class. Resolution rules: if both a factory method and a factory constructor exist, the **factory method wins**. If **several** candidate factory methods are found, they are ambiguous and ignored, so conversion falls back to a constructor if there is one and otherwise fails. So `record Sku(String value) {}` converts for free; a class with only `fromCode(String)` and `fromName(String)` does not. Failures surface as an `ArgumentConversionException` saying there is no built-in converter for `String` → your type. The fix when the fallback can't apply is `@ConvertWith` with an explicit converter — which is also the honest choice when a type shouldn't grow a `String` constructor just to please tests.
code
java · 23 lines// converts: canonical record constructor takes one String
public record Sku(String value) {}
// converts: static single-String factory (name is arbitrary)
public final class Iban {
private final String value;
private Iban(String value) { this.value = value; }
public static Iban parse(String raw) { return new Iban(raw.replace(" ", "")); }
}
// does NOT convert: two candidate factory methods, no String constructor
public final class Account {
private Account(String v) { }
public static Account fromNumber(String n) { return new Account(n); }
public static Account fromAlias(String a) { return new Account(a); }
}
@ParameterizedTest
@CsvSource({"ABC-123, DE44 5001 0517 5407 3249 31"})
void usesFallbackConversion(Sku sku, Iban iban) {
assertEquals("ABC-123", sku.value());
assertNotNull(iban);
}go deeper
Know that a type with a single-String constructor or static factory converts automatically from CSV text.
State the exact requirements — static, non-private, declared in the target type, one String parameter — and that the method wins over the constructor.
Add the failure catalogue (non-static, wrong class, inner class, ambiguous factories, interface-typed parameter) and when to prefer an explicit converter instead.
Discuss the coupling: production API shape becoming load-bearing for tests, silent runtime breakage on refactor, and a team rule for fallback versus @ConvertWith.
## Where the fallback sits JUnit Jupiter's default argument converter first tries its built-in table of `String`-to-target rules (primitives, enums, `java.time`, `UUID`, `Path`, `BigDecimal`, and so on). Only when the target type is *not* in that table does the fallback engage. The fallback is deliberately narrow: it looks inside the target type for exactly one unambiguous way to build an instance from a single `String`. ## The two accepted shapes **Factory method.** A method that is: - declared **in the target type** (not inherited from a superclass, not on some companion/utility class); - **`static`**; - **non-private** (public, protected or package-private all work); - takes **exactly one `String`** parameter; - returns an instance of the target type. The name is irrelevant — `of`, `valueOf`, `parse`, `fromString`, `create` all qualify equally. That is why people are sometimes surprised that a method they never intended as a test hook gets used. **Factory constructor.** A constructor that is: - **non-private**; - takes **exactly one `String`** parameter; - belongs to a **top-level class or a static nested class** (an inner class needs an enclosing instance, so it cannot be built reflectively this way), and the class must not be abstract. A Java `record Sku(String value) {}` satisfies this through its canonical constructor, which is why records make excellent single-value test types. ## Resolution and ambiguity Two rules decide what happens when more than one candidate exists: 1. **Factory method beats factory constructor.** If the type has both `public static Sku of(String)` and `public Sku(String)`, the method is used. Practically this means you can route conversion through validation logic even when a permissive constructor exists. 2. **Multiple factory methods are ambiguous and are therefore ignored.** A type declaring both `fromCode(String)` and `fromDisplayName(String)` provides no usable factory method — JUnit will not guess. If the type also has a single-`String` constructor, the constructor path applies; if it has neither, conversion fails. ## What failure looks like The typical message is a conversion failure stating there is no built-in converter for source type `java.lang.String` and the target type, wrapped in an `ArgumentConversionException` for that invocation. Common causes, in the order I check them: - the factory method is **not static**, or is **private**; - the factory lives on a **different class** (a `SkuFactory` helper) instead of the target type; - the parameter is `CharSequence` or `Object`, not `String`; - the target is an **inner (non-static) class**, so its `String` constructor cannot be used; - the type has **two or more** single-`String` static factory methods and no constructor to fall back on; - the type is **abstract or an interface** — the declared parameter type is what is inspected, so declaring the parameter as an interface defeats the fallback even if the implementation has a factory. ## Design judgment The fallback is attractive because it removes converter boilerplate, but it creates a quiet coupling: a production type's public API is now load-bearing for tests. Two consequences worth stating in an interview: - **Do not add a `String` constructor purely so tests convert.** That widens the production API for a test convenience and often invites unvalidated construction elsewhere. Write an `ArgumentConverter` instead — the coupling then lives in test code where it belongs. - **Do notice when the fallback already applies.** Many value types (`record Email(String value)`, `record Sku(String value)`) already qualify, and adding a converter for them is dead weight. A reasonable team rule: rely on the fallback for types that legitimately have a canonical string form, and use `@ConvertWith` everywhere else. Also be aware that renaming or overloading a factory method on a production type can silently break parameterized tests that depended on it — the failure is at runtime with a conversion message, not at compile time, so the connection is not obvious to whoever made the change. ## Interaction with other mechanisms The fallback also serves reads inside an `ArgumentsAccessor` — `accessor.get(2, Sku.class)` uses the same default converter and therefore the same factory rules. And an explicit `@ConvertWith` converter takes precedence over everything: if the parameter carries one, the built-in table and the fallback are not consulted at all for that parameter.
- A type has both a public single-String constructor and a public static of(String) method. Which does JUnit use, and why does it matter?The factory method is preferred over the constructor. It matters because the factory usually carries normalisation or validation that the constructor may not, so test data goes through the same checks as production input. If you rely on that, keep the factory as the single one of its shape, since adding a second single-String static factory makes both ambiguous and drops you back to the constructor.
- Would you add a String constructor to a production value type just so parameterized tests convert automatically?No. That widens the production API for test convenience and can invite unvalidated construction in real code. The right move is a small ArgumentConverter registered with @ConvertWith, which keeps the coupling inside test code. I would only rely on the fallback when the type genuinely has a canonical string form it would expose anyway.
saying these in an interview costs you the question
- Thinking the factory method must be named valueOf, of, or parse
- Putting the factory on a separate helper class and expecting JUnit to find it
- Believing multiple single-String factory methods make JUnit pick the first one
- Forgetting that an inner (non-static) class cannot be built through the constructor fallback
- Adding a String constructor to production types purely to satisfy test conversion