skip to content

`<system-out>` and `<system-err>` are the only free-text channels the JUnit-XML result format offers a test case. What can they actually carry, and what should you not expect a consumer to do with them?

level: seniorimportance: should knowfreq 48%

answer

  1. two channels, text only
  2. at most one of each per case
  3. no media type, no bytes
  4. one reader ignores <system-err>

basics

~20 s

Each is a plain string element, at most one per <testcase> in the Surefire schema, holding captured output as text -- no media type, no filename, no bytes. And a consumer may read one channel and ignore the other entirely.

solid answer

~40 s

The schema declares both as `xs:string` children of `<testcase>` with `minOccurs=0`, so at most one of each per case, written after any outcome children. Everything in them must survive XML text rules, so binary payloads have no representation and the content is one undifferentiated blob per channel. Writers disagree about the cardinality: JUnit 5's `LegacyXmlReportGeneratingListener` writes a list of `<system-out>` elements per case, and one on `<testsuite>` as well, neither of which the schema declares. Readers disagree about which channel matters: measured on Allure 2, `JunitXmlPlugin` takes the first `<system-out>` on a case and never reads `<system-err>` at all. So anything a reader must see should not live only on standard error.

go deeper

for a junior

Know that <system-out> and <system-err> hold a case's captured output as plain text, that the schema allows at most one of each, and that neither can hold binary data.

for a middle

Explain what the channels cannot express -- no media type, no filename, no structure, no bytes -- and that everything inside must survive XML text escaping.

for a senior

Show you have hit the real trap: a widely used reader takes only the first <system-out> and never reads <system-err>, so valid captured output can be invisible in the report.

for a principal

Be ready to set a house rule for what belongs inline in the result file versus outside it, weighing legibility in place against result files that grow with every logged line.

## What the format gives you The Maven Surefire report schema declares exactly two free-text channels on a `<testcase>`: `<system-out>` and `<system-err>`. Both are plain `xs:string` elements, both are declared `minOccurs="0"` with no `maxOccurs`, which means **at most one of each per case**. They sit last in the element order, after any outcome children. That is the entire provision the format makes for "everything else about this case". There is no element for an attached file, no reference to a file on disk, no media type, no filename, no size, and no ordering beyond the two channel names. ## What they cannot carry Because the content is XML text, several things are simply not expressible: - **Bytes.** A screenshot, a heap dump, a recorded video or any other binary payload has no representation here. Base64 inside a text element technically parses, but nothing that reads these files knows to decode it, so it arrives as a wall of characters. - **Structure.** The channel is one undifferentiated blob per case. Log levels, timestamps and correlation ids survive only as far as they were already text in the captured stream. - **Characters XML forbids.** The content has to survive XML text rules, so writers must do something with characters that are illegal in a document, and what they do differs between them. - **More than one of each per case**, as declared -- which matters, because at least one real writer ignores that. ## Writers disagree about how many JUnit 5's `LegacyXmlReportGeneratingListener` collects a **list** of `<system-out>` contents for a case and writes one element per entry, so a case can end up carrying several `<system-out>` children where the Surefire schema declares one. The same listener also writes a `<system-out>` element on `<testsuite>` itself, and the schema declares no such child there at all. A file like that parses fine. It just does not validate, and a reader written to the schema's cardinality will take one element and leave the others. ## Readers disagree about which they read This is the part that costs teams real time, and it is worth stating concretely rather than as a generality. Measured on Allure 2, its JUnit-XML reader takes **the first `<system-out>`** on a case as that case's log -- and it does not read `<system-err>` at all. There is no code path in that plugin that looks at the element. The consequence is immediate: anything a test writes to standard error, which is where a great deal of logging and stack-trace printing goes by default, is present in the file, valid, correctly escaped, and invisible in the report. The engineer reading the report concludes the information was never captured; the file says otherwise. ## What follows for a team 1. **Put anything a reader must see in `<system-out>`.** Do not rely on `<system-err>` being read; at least one widely-used consumer never looks at it. 2. **Assume one blob per channel.** Even where a writer emits several, expect the reader to use the first. Do not design a scheme that depends on ordering across multiple elements. 3. **Keep it small.** Every byte of captured output is inlined into the XML document. A suite that dumps verbose logs for every case turns a result file that used to be kilobytes into one that is megabytes, and every tool reading it pays that cost on every read. 4. **Do not use either channel for bytes.** The format has no place for them; anything that is not text has to live outside this file and be referenced by a convention the format does not define. 5. **Do not assume the two channels are interleaved.** They are separate elements written separately, so the relative ordering of a line on standard out and a line on standard error is lost even when both are captured. ## A useful mental summary `<system-out>` and `<system-err>` are the format's escape hatch, and like most escape hatches they are unstructured, weakly specified and inconsistently honoured. They are genuinely useful for a few lines of context that make a failure legible in place -- a request id, the parameters of the case, the branch a fixture took. They are a poor place for anything you would be upset to lose, because the format does not promise it will be read, does not promise how many of them may exist, and does not promise what a writer did to the characters inside.

  • Where does JUnit 5's legacy listener put a case's captured output, and does it respect the schema's cardinality?
    Into `<system-out>` and `<system-err>` children of `<testcase>`, but written from a list rather than as a single element, so one case can carry several `<system-out>` children. It also writes a `<system-out>` on `<testsuite>`, which the Surefire schema does not declare there at all.
  • What is the practical rule if a consumer reads only `<system-out>`?
    Route anything a reader must see to standard out rather than standard error, and keep it short -- it is one unstructured blob per case, inlined into the XML, and every tool pays for its size on every read. Never put bytes in either channel; the format has no representation for them.

saying these in an interview costs you the question

  • Says <system-out> can carry a screenshot or other binary payload
  • Assumes every consumer reads both <system-out> and <system-err>
  • Claims the schema allows many <system-out> children per case
  • Expects standard out and standard error to stay interleaved