JUnit 5's @CsvSource accepts either an array of row strings or a single textBlock. How do the two forms differ, and how would you feed it data whose values contain commas or use some other separator character?
answer
- value = String[] rows; textBlock = one block, lines are rows
- textBlock: # lines are comments, trailing whitespace stripped
- Quote char is ' for @CsvSource; '' escapes a literal quote
- delimiter (char) or delimiterString (String) — never both
- ignoreLeadingAndTrailingWhitespace defaults to true
basics
~20 sThe array form lists rows as separate quoted strings. The textBlock form puts the whole table in one Java text block, one row per line, so it reads like a real CSV table and supports # comment lines. For odd data, set delimiter/delimiterString or quoteCharacter, or quote the value.
solid answer
~50 sBoth forms feed the same parser. `value` takes a `String[]` — each element is a row. `textBlock` takes a single Java text block whose *lines* are the rows, so the table is aligned and readable, blank trailing whitespace is stripped, and lines starting with `#` are treated as comments and skipped. You use one or the other, never both. For awkward data there are three levers: - **Quote the value.** `@CsvSource`'s quote character is the single quote, so `'lemon, lime'` is one column. `quoteCharacter` changes it — useful when the data is full of apostrophes. - **Change the separator.** `delimiter` takes a single `char` (e.g. `delimiter = '|'`); `delimiterString` takes a multi-character separator such as `"||"`. Set one, not both. - **Whitespace.** Columns are trimmed by default; quote a value or set `ignoreLeadingAndTrailingWhitespace = false` to keep padding.
code
java · 10 lines@ParameterizedTest
@CsvSource(delimiter = '|', textBlock = """
# input | expected length
Hello, World! | 13
Goodbye | 7
'' | 0
""")
void measures(String input, int expectedLength) {
assertEquals(expectedLength, input.length());
}go deeper
Know that both forms exist, that the text block is one row per line, and that quoting handles a comma inside a value.
Name the defaults and the switches — delimiter/delimiterString, quoteCharacter, ignoreLeadingAndTrailingWhitespace — and the fact that '' escapes a quote.
Diagnose from symptoms: an argument-count failure usually means an unquoted delimiter; prefer changing the delimiter over escaping a table full of commas.
Treat the fixture as something reviewers read: alignment, comments and a delimiter absent from the data are what keep a large table maintainable, and past a screenful it belongs outside the annotation.
## Two spellings, one parser `@CsvSource` has two mutually exclusive ways to hold the table. **Array form** — the original: ```java @CsvSource({ "apple, 1", "banana, 2", "'lemon, lime', 0xF1" }) ``` Each array element is one row. It works, but every row carries quotes and a trailing comma, so wide tables become noisy and diffs are ugly. **Text-block form** — added in JUnit 5.8.1, on top of Java text blocks: ```java @CsvSource(textBlock = """ apple, 1 banana, 2 'lemon, lime', 0xF1 """) ``` Here the *lines* of the block are the rows. The table looks like a table: columns line up, adding a case is a one-line diff, and there is no per-row quoting ceremony. Two extra conveniences come with it — trailing whitespace on each line is stripped, and **any line beginning with `#` is treated as a comment and ignored**, which lets you annotate or temporarily disable a case in place. Set `value` or `textBlock`, never both; declaring both is a configuration error. ## When the data fights the format CSV has exactly two structural characters — the delimiter and the quote — and real data collides with both. ### Values containing the delimiter Quote them. `@CsvSource`'s quote character is the **single quote** `'` (this is the classic trap: `@CsvFileSource` defaults to the double quote `"` instead). So `'lemon, lime', citrus` is two columns. To put a literal quote character inside a quoted value, double it: `'it''s here'`. If your data is full of apostrophes, flip the quote character instead of escaping everything: ```java @CsvSource(quoteCharacter = '"', textBlock = """ "it's fine", ok "", empty """) ``` `quoteCharacter` was added in 5.8 and applies to both `@CsvSource` and `@CsvFileSource`. ### A different separator entirely When commas are pervasive in the data, stop using them as the separator: ```java @CsvSource(delimiter = '|', textBlock = """ Hello, World! | 13 Goodbye | 7 """) ``` `delimiter` is a single `char`. For a multi-character separator use `delimiterString = "||"`. Setting both `delimiter` and `delimiterString` is a configuration error — pick one. A pipe or a tab is often the pragmatic choice for tables containing prose, and it removes the need for quoting altogether. ### Whitespace By default `ignoreLeadingAndTrailingWhitespace` is `true`, which is exactly what makes aligned tables possible: the padding you add for readability never reaches the test. When a case genuinely tests padding, either quote the value (`' padded '` keeps its spaces because trimming happens outside the quotes) or set `ignoreLeadingAndTrailingWhitespace = false` for the whole annotation — and then align nothing. ## Choosing between the forms Use the text block whenever the table is more than two or three rows or more than two columns: alignment and `#` comments pay for themselves immediately, and reviewers can read the fixture as data instead of as escaped strings. The array form is still fine for one or two rows, and it is what you will meet in older codebases, so you should be able to read both. The text block does not change semantics: same delimiter rules, same quoting rules, same trimming, same conversion of each column to the declared parameter type. It is purely a better container. ## Failure modes to recognise - **Row and parameter counts disagree.** A stray delimiter inside an unquoted value silently creates an extra column, and the invocation fails on argument count — look for an unquoted comma first. - **Wrong quote character.** Using `"..."` in `@CsvSource` without setting `quoteCharacter` does not quote anything; the double quotes become part of the value (and, inside the array form, must be escaped as `\"` just to compile). - **Both `value` and `textBlock`, or both `delimiter` and `delimiterString`** — configuration errors reported before any invocation runs. - **Losing alignment padding you meant to test** — trimming is on by default. ## Rule of thumb Pick the separator your data does *not* contain, prefer the text block, keep the cells literal, and reach for `@CsvFileSource` or `@MethodSource` the moment the table stops fitting comfortably in the annotation.
- Can you set both delimiter and delimiterString on the same @CsvSource?No. `delimiter` is a single char and `delimiterString` is a multi-character separator, and configuring both is rejected as a configuration error before any invocation runs. Choose `delimiter` for one character such as '|' or a tab, and `delimiterString` only when the separator is genuinely longer, like "||".
- How do you include a literal single quote inside a quoted @CsvSource value?Double it: `'it''s here'` produces `it's here`. If the data is thick with apostrophes, it is cleaner to set `quoteCharacter = '"'` and quote with double quotes instead, or to change the delimiter so quoting is not needed at all.
saying these in an interview costs you the question
- Using double quotes to quote a @CsvSource value without setting quoteCharacter
- Thinking textBlock changes the parsing rules rather than just the container
- Setting both value and textBlock, or both delimiter and delimiterString
- Claiming trimming is off by default, then relying on padding inside unquoted columns
- Escaping delimiters with a backslash, which @CsvSource does not support