{"title":"Use JUnit Parameterized Tests Without Matrix Explosion","excerpt":"A parameterized test that runs 288 times reports its failures as a coordinate rather than a claim, and by default Jupiter throws away any column your method forgot to declare — silently, with the test still passing. This guide covers choosing rows by the rule each one defends rather than by crossing dimensions, switching argument count validation to strict, why a CSV value is quoted in the display name before it is converted, keeping the expected value out of production code, and the three signals that a table should have been two tests.","canonicalUrl":"https://automationtester.in/blog/automation-tutorials/junit-parameterized-tests-without-matrix-explosion","category":{"name":"Automation Tutorials","slug":"automation-tutorials"},"tags":["junit","java","jvm","parameterized-tests","test-design","boundary-testing"],"author":{"name":"Shashank Rawlani","url":"https://shashank.rawlani.com","linkedin":"https://www.linkedin.com/in/shashankrawlani/"},"datePublished":"2026-09-27T13:30:00.000Z","dateModified":"2026-10-14T11:09:52.936Z","coverImage":{"url":"https://ik.imagekit.io/automationtester/automationtester-in/blog/covers/v3/junit-parameterized-tests-without-matrix-explosion.webp","alt":"Dark technical illustration split by a dashed green vertical line with a small ringed dot at each end. The left two-thirds are a dense lattice of small rounded grey squares, thirteen columns by fifteen rows, sitting on a grid of thin rules that run off the top, bottom and left edges, with a grey dot centred in every square. One whole column of that lattice is orange instead: brighter squares and dots threaded by a thick dashed orange vertical line, capped at top and bottom by a crossed-out orange ring. On the right of the dashed boundary, a thick green line rises left to right in four square steps and continues off the right edge; the area beneath it is filled with a faint green wash and thin vertical hatching down to a grey horizontal baseline that also runs off the right edge. A large ringed green dot sits on each of the four step corners with two green pills of decreasing length beside it, and a faint dashed green line drops from each corner to the baseline. Below the baseline, four short green pills sit in a row. Two faint green pills sit at the top of the right band, and thin grey rules cross the top and bottom of the whole frame."},"contentHtml":"<div class=\"callout callout-info\"><strong>Quick answer:</strong> Jupiter ships no Cartesian-product source, so a matrix is always something you built. Pick rows that sit on a rule's boundary instead of crossing every dimension, turn on <code class=\"language-text\">junit.jupiter.params.argumentCountValidation = strict</code> so a column you forgot to consume raises instead of being discarded, and give every row a name that survives being reordered. Two dimensions that need different setup or different expected values are two parameterized tests, not one wider table.</div>\n\n<p>Here is a rounding test that runs 288 times and cannot tell you which rule broke:</p>\n\n<pre class=\"language-java\"><code class=\"language-java\">@ParameterizedTest\n@MethodSource(\"allCombinations\")   // 4 currencies x 8 amounts x 3 modes x 3 signs\nvoid rounding(String currency, String amount, RoundingMode mode, int sign) {\n    Money money = Money.of(currency, new BigDecimal(amount).multiply(BigDecimal.valueOf(sign)));\n    assertEquals(expected(currency, amount, mode, sign), money.rounded(mode).toString());\n}</code></pre>\n\n<p>When it fails, the report says <code class=\"language-text\">[173] currency = \"JPY\", amount = \"10.5\", mode = HALF_EVEN, sign = -1</code>. Case 173 is a coordinate, not a claim. It does not say which business rule that combination was defending, whether the other 287 rows covered the same rule, or whether <code class=\"language-text\">expected(...)</code>, which is where the real logic went, is right. And the run takes 288 times as long as the one row that mattered.</p>\n\n<p>The problem is not the parameterized-test feature. It is that a product of dimensions was written where a list of rules belonged.</p>\n\n<div class=\"callout callout-warning\"><strong>Turn on strict argument count validation before you audit anything else.</strong> By default a source may supply more arguments than the method declares, and the surplus is dropped without a word. The guide's own phrasing is that this \"can lead to bugs where arguments are never passed\". A four-column table feeding a three-parameter method has been testing three columns for as long as it has existed, and it has been green the whole time. Verified against the JUnit 6.1.3 user guide on 2026-09-07; the quoting behaviour described later arrived in Jupiter 6.0, so a 5.x suite will render display names differently.</div>\n\n<h2 id=\"what-a-large-argument-source-actually-buys\">What a large argument source actually buys</h2>\n\n<p>Every invocation of a parameterized test has the full lifecycle of a regular one: \"<code class=\"language-text\">@BeforeEach</code> methods will be executed before each invocation\", and each invocation appears separately in the tree. That is the cost side. A row is a whole test, priced like a whole test.</p>\n\n<p>What you get for it is a report that names the input. That is genuinely valuable, and it is the only thing a row buys that a loop inside one test method does not. So the question for every row is whether its input, printed in a failure, would tell a reader something the neighbouring rows do not.</p>\n\n<p>Currency rounding has four such inputs and they are not a grid:</p>\n\n<pre class=\"language-java\"><code class=\"language-java\">@ParameterizedTest(name = \"{index} {0} {1} at {2} rounds to {3}\")\n@CsvSource(useHeadersInDisplayName = true, textBlock = \"\"\"\n    CURRENCY, AMOUNT,  MODE,      EXPECTED\n    USD,      10.005,  HALF_UP,   10.01\n    USD,      10.005,  HALF_EVEN, 10.00\n    JPY,      10.5,    HALF_UP,   11\n    USD,      -10.005, HALF_UP,   -10.01\n    \"\"\")\nvoid roundsToTheCurrencysMinorUnit(Currency currency, BigDecimal amount,\n                                   RoundingMode mode, BigDecimal expected) {\n    assertEquals(0, expected.compareTo(Money.of(currency, amount).rounded(mode)));\n}</code></pre>\n\n<p>Four rows, four distinct claims. Row one and row two differ only in the mode and prove that the mode is honoured. That is a pair no single row can establish, because 10.005 is the smallest amount where <code class=\"language-text\">HALF_UP</code> and <code class=\"language-text\">HALF_EVEN</code> disagree at two digits. Row three proves the scale comes from the currency rather than from a constant 2: the Java <code class=\"language-text\">Currency</code> API is explicit that its fraction digits are \"the same as ISO 4217's minor unit for the currency\" and that \"the default number of fraction digits for the Euro is 2, while for the Japanese Yen it's 0\". Row four proves the sign is handled by rounding away from zero rather than toward it.</p>\n\n<p>Add EUR and you add a row that duplicates row one's claim with a different letter. Add GBP and you add another. That is the test for whether a row belongs: name the rule it defends, and if the name matches an existing row's, delete it.</p>\n\n<h2 id=\"extra-columns-are-thrown-away-in-silence\">Extra columns are thrown away in silence</h2>\n\n<p>There is one default in Jupiter's parameterized support that will eventually cost you an afternoon. When a source supplies more arguments than the method declares, the surplus is discarded and the test runs anyway. The guide names the consequence directly: \"this can lead to bugs where arguments are never passed to the parameterized class or method.\"</p>\n\n<p>Which means this compiles, runs, passes, and tests nothing about the mode:</p>\n\n<pre class=\"language-java\"><code class=\"language-java\">// Four columns, three parameters. MODE is silently dropped and every row\n// is evaluated with whatever default rounded() uses.\n@ParameterizedTest\n@CsvSource({ \"USD, 10.005, HALF_EVEN, 10.00\" })\nvoid rounding(Currency currency, BigDecimal amount, BigDecimal expected) { }</code></pre>\n\n<p>Turn it into an error once, in <code class=\"language-text\">src/test/resources/junit-platform.properties</code>:</p>\n\n<pre class=\"language-properties\"><code class=\"language-properties\">junit.jupiter.params.argumentCountValidation = strict</code></pre>\n\n<p>The same setting exists per annotation as <code class=\"language-text\">@ParameterizedTest(argumentCountValidation = ArgumentCountValidationMode.STRICT)</code>, which is the right form while you are migrating a suite that has years of accumulated columns in it. Project-wide is the right form afterwards.</p>\n\n<h2 id=\"pick-boundaries-then-let-the-source-follow\">Pick boundaries, then let the source follow</h2>\n\n<p>Choose the values first and the annotation second. Jupiter's sources are not interchangeable, and each one is good at a different kind of set.</p>\n\n<p><code class=\"language-text\">@ValueSource</code> takes one literal array and can supply only a single argument per invocation, which makes it right for a one-dimensional boundary sweep and wrong for anything else. Its supported types are the primitives plus <code class=\"language-text\">String</code> and <code class=\"language-text\">Class</code>.</p>\n\n<p><code class=\"language-text\">@EnumSource</code> is the one people under-use. Rather than listing constants by hand, it takes a range: \"the range starts from the constant specified in the <code class=\"language-text\">from</code> attribute and includes all subsequent constants up to and including the one specified in the <code class=\"language-text\">to</code> attribute, based on the natural order of the enum constants\", and <code class=\"language-text\">mode = EXCLUDE</code> subtracts from that range.</p>\n\n<pre class=\"language-java\"><code class=\"language-java\">// Every rounding mode that can move a half-way value, minus the one whose\n// behaviour is asserted separately because it throws.\n@ParameterizedTest\n@EnumSource(mode = EXCLUDE, names = { \"UNNECESSARY\" })\nvoid everyModeRoundsAHalfwayValueToTheMinorUnit(RoundingMode mode) {\n    assertDoesNotThrow(() -&gt; Money.of(Currency.getInstance(\"USD\"),\n            new BigDecimal(\"10.005\")).rounded(mode));\n}</code></pre>\n\n<p>The value of that over an explicit list is that it stays correct when the enum grows. A new constant joins the run automatically and either passes or tells you it needs a rule.</p>\n\n<p>For null and empty handling, use the dedicated sources rather than smuggling sentinels through CSV. <code class=\"language-text\">@NullSource</code>, <code class=\"language-text\">@EmptySource</code> and the composed <code class=\"language-text\">@NullAndEmptySource</code> are repeatable alongside <code class=\"language-text\">@ValueSource</code>, and the guide's own example, <code class=\"language-text\">@NullAndEmptySource</code> plus four blank strings, produces exactly six invocations rather than a hand-maintained list. Note that <code class=\"language-text\">@NullSource</code> \"cannot be used for a parameter that has a primitive type\", which is the correct compile-time pressure to declare the parameter as the boxed type when null is a real input.</p>\n\n<p><code class=\"language-text\">@MethodSource</code> is for rows a literal cannot express: a set built from a domain type, or one that closes a resource. Two constraints decide whether it is safe. The factory \"must be static unless the test class is annotated with <code class=\"language-text\">@TestInstance(Lifecycle.PER_CLASS)</code>\", and if it returns a <code class=\"language-text\">Stream</code>, \"JUnit will properly close it by calling <code class=\"language-text\">BaseStream.close()</code>, making it safe to use a resource such as <code class=\"language-text\">Files.lines()</code>.\"</p>\n\n<p>What a factory must not do is read a system whose contents can change between runs. A method source that queries a database produces a row count that varies, display names that vary with it, and a green run that proves the fixture was empty. If the cases come from data, snapshot the data into the repository and let the factory read the snapshot.</p>\n\n<h2 id=\"the-name-is-what-survives-a-reorder\">The name is what survives a reorder</h2>\n\n<p>A row's index is not an identity. Insert a case at the top and every subsequent row's name changes, so a CI history keyed on <code class=\"language-text\">[7]</code> now points at a different claim. Two mechanisms fix that, and both are worth the few characters.</p>\n\n<p>The first is parameter names, which are not in the bytecode by default. The guide is specific: \"to ensure that parameter names are present in the bytecode, test code must be compiled with the <code class=\"language-text\">-parameters</code> compiler flag for Java or with the <code class=\"language-text\">-java-parameters</code> compiler flag for Kotlin.\" Without it you get bare values; with it you get <code class=\"language-text\">currency = \"JPY\"</code>.</p>\n\n<pre class=\"language-groovy\"><code class=\"language-groovy\">tasks.withType(JavaCompile).configureEach {\n    options.compilerArgs.add('-parameters')\n}</code></pre>\n\n<p>The second is naming rows explicitly. <code class=\"language-text\">Named</code> labels one argument and <code class=\"language-text\">argumentSet</code> labels a whole row, and the corresponding placeholders are <code class=\"language-text\">{argumentSetName}</code> and <code class=\"language-text\">{argumentSetNameOrArgumentsWithNames}</code> alongside the more familiar <code class=\"language-text\">{index}</code>, <code class=\"language-text\">{arguments}</code>, <code class=\"language-text\">{argumentsWithNames}</code> and positional <code class=\"language-text\">{0}</code>.</p>\n\n<pre class=\"language-java\"><code class=\"language-java\">static Stream&lt;Arguments&gt; roundingRules() {\n    return Stream.of(\n        argumentSet(\"half-up breaks the tie upward\",\n                usd(), bd(\"10.005\"), HALF_UP,   bd(\"10.01\")),\n        argumentSet(\"half-even breaks the tie to the even digit\",\n                usd(), bd(\"10.005\"), HALF_EVEN, bd(\"10.00\")),\n        argumentSet(\"a zero-minor-unit currency rounds to whole units\",\n                jpy(), bd(\"10.5\"),   HALF_UP,   bd(\"11\")),\n        argumentSet(\"a refund rounds away from zero\",\n                usd(), bd(\"-10.005\"), HALF_UP,  bd(\"-10.01\"))\n    );\n}</code></pre>\n\n<p>Now a failure reads \"a zero-minor-unit currency rounds to whole units\" and the regression is traceable across a reorder, a rename and a year. Two details will bite while you set this up. The <code class=\"language-text\">name</code> attribute \"is a <code class=\"language-text\">MessageFormat</code> pattern. Thus, a single quote (<code class=\"language-text\">'</code>) needs to be represented as a doubled single quote (<code class=\"language-text\">''</code>)\". So an apostrophe in a display name silently swallows the text after it. And argument strings in names are truncated at <code class=\"language-text\">junit.jupiter.params.displayname.argument.maxlength</code>, which defaults to 512 characters, so a row identified only by a large JSON payload becomes indistinguishable from its neighbour.</p>\n\n<h2 id=\"csv-values-are-quoted-before-they-are-converted\">CSV values are quoted before they are converted</h2>\n\n<p>One more thing about display names that surprises everyone the first time. As of Jupiter 6.0, text arguments in parameterized display names are quoted by default: a <code class=\"language-text\">CharSequence</code> is wrapped in double quotes, a <code class=\"language-text\">Character</code> in single quotes, and control characters are escaped, so a tab appears as <code class=\"language-text\">\"\\t\"</code> rather than as invisible whitespace. That is a straightforward improvement. The part to internalise is when it happens:</p>\n\n<blockquote><p>The original source arguments are quoted when generating a display name, and this occurs before any implicit or explicit argument conversion is performed.</p></blockquote>\n\n<p>So a row written <code class=\"language-text\">0xF1</code> and consumed as an <code class=\"language-text\">int</code> reports as <code class=\"language-text\">rank = \"0xF1\"</code>, not <code class=\"language-text\">241</code>. The display name shows what you typed; the assertion sees what it converted to. When those two differ enough to confuse a reader, put the converted value in the row name yourself with <code class=\"language-text\">argumentSet</code>. The behaviour can be switched off per annotation via <code class=\"language-text\">quoteTextArguments = false</code>, which is occasionally the right call for a suite whose snapshots contain the old unquoted names.</p>\n\n<p>The conversion itself is worth knowing precisely, because it is what lets a CSV row carry domain types instead of strings. Implicit conversion covers the primitives, enums, <code class=\"language-text\">BigDecimal</code>, <code class=\"language-text\">java.util.Currency</code> (<code class=\"language-text\">\"JPY\"</code> becomes <code class=\"language-text\">Currency.getInstance(\"JPY\")</code>), the <code class=\"language-text\">java.time</code> types, <code class=\"language-text\">Path</code>, <code class=\"language-text\">URI</code> and more. Beyond that list there is a fallback: a target type with \"a non-private, static method declared in the target type that accepts either a single <code class=\"language-text\">String</code> argument or a single <code class=\"language-text\">CharSequence</code> argument and returns an instance of the target type\" is constructed from the column automatically. Give <code class=\"language-text\">Money</code> a <code class=\"language-text\">parse</code> method and <code class=\"language-text\">USD 10.005</code> becomes a <code class=\"language-text\">Money</code> with no converter to write.</p>\n\n<p>The null rules are the ones to memorise, because they are asymmetric. An unquoted empty column \"will always be converted to a null reference regardless of any custom values configured via the <code class=\"language-text\">nullValues</code> attribute\". A quoted empty value <code class=\"language-text\">''</code> yields an empty string unless <code class=\"language-text\">emptyValue</code> says otherwise. And \"an <code class=\"language-text\">ArgumentConversionException</code> is thrown if the target type of a null reference is a primitive type\", which is how a trailing comma in a text block turns into a failure that names conversion rather than the column you actually mistyped.</p>\n\n<h2 id=\"keep-the-expected-value-out-of-the-implementation\">Keep the expected value out of the implementation</h2>\n\n<p>The original 288-row example had a second defect that the row count hid. <code class=\"language-text\">expected(currency, amount, mode, sign)</code> is a second implementation of rounding, written by the same person, on the same day, with the same misunderstanding available to it. When both agree, the test proves they agree.</p>\n\n<p>A literal in a column cannot do that. Neither can a value derived from a published rule: ISO 4217's minor unit, a rate table from the tax authority, a worked example in a specification. The discipline is small and absolute: the expected column is typed by a human from a source outside the codebase, or the row is not evidence.</p>\n\n<p>The exception worth allowing is a <em>property</em> rather than an oracle. \"Rounding twice at the same scale equals rounding once\" and \"the rounded value differs from the input by less than one minor unit\" are assertions that need no expected column at all, and they hold across the whole input space. Those belong in their own test, next to the table rather than inside it, because a property that fails tells you something a row cannot.</p>\n\n<h2 id=\"when-a-row-should-have-been-its-own-test\">When a row should have been its own test</h2>\n\n<p>Rows share a body. That is the whole mechanism, and it is also the constraint that decides when a table has been stretched too far. Split when any of three things is true.</p>\n\n<p>The setup differs. If half the rows need a promotion loaded and half must not have one, the <code class=\"language-text\">@BeforeEach</code> ends up branching on a parameter, and a lifecycle method that reads its own test's arguments is a design that will not survive the next case.</p>\n\n<p>The oracle differs in kind. A row that expects a value and a row that expects an exception are not the same test with different data. <code class=\"language-text\">assertThrows</code> and <code class=\"language-text\">assertEquals</code> are different assertions. A nullable expected column with an <code class=\"language-text\">if</code> around it is the smell.</p>\n\n<p>The dimensions are independent. Currency and rounding mode interact, which is why they share a table. Currency and locale do not, so testing all pairs multiplies the run to establish nothing that either dimension alone would miss.</p>\n\n<p>There is a fourth case where the answer is a different annotation entirely. When several test methods in a class all need the same parameter, <code class=\"language-text\">@ParameterizedClass</code> parameterizes the container instead, with the value injected into a <code class=\"language-text\">@Parameter</code> field or the constructor. It is worth knowing about and worth waiting on: the guide marks it experimental, and pinning a suite's structure to an experimental feature is a migration you will pay for twice.</p>\n\n<h2 id=\"apply-this-now\">Apply this now</h2>\n\n<p>Set <code class=\"language-text\">junit.jupiter.params.argumentCountValidation = strict</code> and run the suite. Every failure it produces is a column that was never reaching a parameter, and each one is a test that has been asserting less than its source claimed, sometimes for years. Fix them before touching anything else, because until they are fixed you cannot trust the rest of the audit.</p>\n\n<p>Then take the parameterized test in your suite with the most rows and write, beside each row, the one sentence it proves. Rows whose sentences match another row's are deletions. Rows you cannot write a sentence for are the ones that came from a product rather than a rule, and they are usually the majority.</p>\n\n<p>Add <code class=\"language-text\">-parameters</code> to the compiler arguments in the same change, so the rows that survive report their inputs by name. The run should get shorter and the failure output should get longer, which is the trade this whole exercise is making.</p>\n\n<h2 id=\"questions-about-parameterized-test-design\">Questions about parameterized test design</h2>\n\n<h3 id=\"how-many-rows-is-too-many-for-one-test\">How many rows is too many for one test?</h3>\n\n<p>There is no count, but there is a check: if you cannot state what each row proves without looking at the implementation, the table is too big regardless of size. A forty-row table generated from a published rate schedule is fine, because every row traces to a line in the schedule. A twelve-row table built by crossing three flags with four inputs is not, because eight of those rows exist only because the other four did. The same crossing happens one level up when CI runs the suite per JDK and operating system; the <a href=\"https://automationtester.in/tools/github-actions-matrix-preview\">GitHub Actions Matrix Preview</a> shows the jobs a <code class=\"language-text\">strategy.matrix</code> multiplies into before they run.</p>\n\n<h3 id=\"should-boundary-values-live-in-the-annotation-or-a-constant\">Should boundary values live in the annotation or in a constant?</h3>\n\n<p>In the annotation, spelled out. A row reading <code class=\"language-text\">BULK_THRESHOLD, 17.00</code> passes when the constant moves, because both sides moved together. That is the same defect as an oracle that calls production code. Literal boundary values are one of the few places where duplicating a constant is correct, and the duplication is the point: the test is where the rule is written down.</p>\n\n<h3 id=\"what-about-generating-rows-from-a-specification-file\">What about generating rows from a specification file?</h3>\n\n<p>Reasonable, with one condition: the file has to be in version control next to the test. <code class=\"language-text\">@CsvFileSource</code> reads either a classpath resource or a path on disk, skips headers via <code class=\"language-text\">numLinesToSkip</code> and treats lines starting with its <code class=\"language-text\">commentCharacter</code>, <code class=\"language-text\">#</code> by default, as comments, so the fixture can explain itself. A file committed alongside the code gives you a diff when the cases change, which is the property a database query cannot offer. Watch one inconsistency while porting rows into it: <code class=\"language-text\">@CsvFileSource</code> uses a double quote as its quote character by default, where <code class=\"language-text\">@CsvSource</code> uses a single quote.</p>\n\n<h3 id=\"do-parameterized-tests-work-with-concurrent-execution\">Do parameterized tests run concurrently with each other?</h3>\n\n<p>Invocations are nodes in the test tree like any others, so they follow the same execution mode rules and the same <code class=\"language-text\">@Execution</code> inheritance. The practical consequence is that a table sharing a mutable field across rows becomes a race the moment someone enables concurrency at the class level. Rows that need shared state are rows that should have been separate tests; part three of this series covers how that failure presents.</p>\n\n<h3 id=\"is-a-loop-inside-one-test-ever-the-better-choice\">Is a loop inside one test ever the better choice?</h3>\n\n<p>When the inputs are not individually interesting. Checking that a codec round-trips a thousand random byte arrays does not benefit from a thousand entries in the report. The failure you care about is the first mismatch, and one test with a loop reports it just as well while costing one node instead of a thousand. Reach for a parameterized test when each input has a name; reach for a loop when the input is a sample.</p>\n\n<h2 id=\"primary-references\">Primary references</h2>\n\n<ul>\n<li><a href=\"https://docs.junit.org/current/writing-tests/parameterized-classes-and-tests.html\">Parameterized Classes and Tests — JUnit 6.1.3 user guide</a>. Argument count validation and the warning that surplus arguments are ignored, so \"this can lead to bugs where arguments are never passed\"; the <code class=\"language-text\">junit.jupiter.params.argumentCountValidation</code> parameter and the <code class=\"language-text\">STRICT</code> annotation attribute. The <code class=\"language-text\">@ValueSource</code> supported types and single-argument limit; <code class=\"language-text\">@NullSource</code>, <code class=\"language-text\">@EmptySource</code> and <code class=\"language-text\">@NullAndEmptySource</code> including that <code class=\"language-text\">@NullSource</code> cannot target a primitive; the <code class=\"language-text\">@EnumSource</code> <code class=\"language-text\">from</code>/<code class=\"language-text\">to</code> range semantics and <code class=\"language-text\">mode = EXCLUDE</code>; the <code class=\"language-text\">@MethodSource</code> static-factory rule and stream closing via <code class=\"language-text\">BaseStream.close()</code>; the <code class=\"language-text\">@CsvSource</code> quoting, trimming and null rules including that an unquoted empty value is always null and that a null bound to a primitive raises <code class=\"language-text\">ArgumentConversionException</code>; <code class=\"language-text\">textBlock</code> with <code class=\"language-text\">useHeadersInDisplayName</code>; the implicit conversion table including <code class=\"language-text\">java.util.Currency</code> and <code class=\"language-text\">BigDecimal</code>, and the fallback String-to-object factory method or constructor; the display-name placeholders, the <code class=\"language-text\">MessageFormat</code> doubled-quote rule, the <code class=\"language-text\">-parameters</code> compiler flag requirement and the 512-character <code class=\"language-text\">displayname.argument.maxlength</code> default; the <code class=\"language-text\">Named</code> API and <code class=\"language-text\">argumentSet(String, Object…)</code>, a static factory on <code class=\"language-text\">org.junit.jupiter.params.provider.Arguments</code>; the <code class=\"language-text\">@CsvFileSource</code> <code class=\"language-text\">resources</code>, <code class=\"language-text\">files</code>, <code class=\"language-text\">numLinesToSkip</code> and <code class=\"language-text\">commentCharacter</code> attributes and its double-quote default; that quoting of text arguments became the default in Jupiter 6.0 and \"occurs before any implicit or explicit argument conversion is performed\"; that each invocation has the same lifecycle as a regular <code class=\"language-text\">@Test</code>; and that <code class=\"language-text\">@ParameterizedClass</code> is an experimental feature.</li>\n<li><a href=\"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Currency.html\">Java SE — <code class=\"language-text\">java.util.Currency</code></a>. That <code class=\"language-text\">getDefaultFractionDigits()</code> returns a value where \"the number of fraction digits is the same as ISO 4217's minor unit for the currency\", and that \"the default number of fraction digits for the Euro is 2, while for the Japanese Yen it's 0\" — the fact the JPY row in the worked table exists to protect.</li>\n<li><a href=\"https://docs.junit.org/current/writing-tests/parallel-execution.html\">Parallel Execution — JUnit 6.1.3 user guide</a>. That <code class=\"language-text\">@Execution</code> applies to the annotated element \"and its subelements\", which is what makes a parameterized test's invocations inherit their container's execution mode.</li>\n<li><a href=\"https://docs.gradle.org/current/userguide/java_testing.html\">Gradle 9.7.1 — the Test task and useJUnitPlatform()</a>. That <code class=\"language-text\">useJUnitPlatform()</code> is what makes a Gradle <code class=\"language-text\">Test</code> task run Jupiter at all, and that every JVM test in Gradle runs through a <code class=\"language-text\">Test</code> task whose framework and compiler settings you configure — the wiring the <code class=\"language-text\">-parameters</code> snippet slots into. Gradle 9.7.1.</li>\n</ul>\n"}