A dashboard shows a red case from a JUnit-XML file with an empty failure message although the stack trace is there. In `<failure>` and `<error>`, where do the message, the exception type and the trace each live, and what makes the message go missing?
answer
- three values, three places
- attributes carry the summary
- the trace is the element's text
- no throwable message means no @message
basics
~20 sBoth elements are simple content: the stack trace is the element's own text, while the message and exception type ride on the optional @message and @type attributes. A throwable whose message is null means no @message is written at all.
solid answer
~40 sThe schema declares `<failure>` and `<error>` as simple-content extensions of `xs:string` with optional `@message` and `@type`, so the trace is the element's text and the other two values are attributes. JUnit 5's `LegacyXmlReportGeneratingListener` writes `@message` only when the throwable's message is non-null, and always writes `@type` as the throwable's class name; Maven Surefire writes `@message` only when it is non-null and non-empty. Consumers copy those attributes straight through -- Allure 2's `JunitXmlPlugin` takes the first outcome element present, puts its `@message` into the message field and its text into the trace -- so a bare `NullPointerException` or a description-less assertion produces exactly this symptom: full trace, blank message. `<skipped>` declares `@message` but no `@type`.
code
xml · 4 lines<testcase name="readsProfile" classname="com.example.ProfileTest" time="0.007">
<failure type="java.lang.NullPointerException">java.lang.NullPointerException
at com.example.ProfileTest.readsProfile(ProfileTest.java:64)</failure>
</testcase>go deeper
Know the three places: @message holds the message, @type holds the exception type, and the stack trace is the element's own text content. All three are optional.
Explain why a message can be absent -- the writer omits @message when the throwable's message is null or empty -- and that an element may legitimately be nil with nothing in it at all.
Diagnose the symptom end to end: work out whether the writer never wrote the attribute or the reader never looked at it, knowing that a reader typically takes the first outcome element and stops.
Be ready to set the convention that makes failures legible across a fleet of suites -- non-null assertion messages, an agreed fallback when @message is absent, and no parsing of @type as a class name.
## Three values, three different places `<failure>` and `<error>` are declared in `surefire-test-report.xsd` as **simple-content** elements: a complex type extending `xs:string` with two optional attributes. That single sentence decides where each part of a failure lands. | what | where it lives | required? | |---|---|---| | the exception's message | the `@message` attribute | optional | | the exception's type | the `@type` attribute | optional | | the stack trace | the element's own **text content** | optional | `<skipped>` is the same shape with one difference: it declares `@message` only and no `@type` -- a skip has no exception type to name. Note what is *not* in that table. There is no `<stackTrace>` child under `<failure>` or `<error>`; their content is text directly. There is no `@line`, no `@file`, no structured actual-versus-expected pair. Everything beyond the message and the type is one unparsed string. ## Why the message goes missing A blank message beside a full stack trace is not a rendering bug. It is what both writers produce under ordinary conditions, and there are four distinct ways to get there. 1. **The throwable had no message.** JUnit 5's `LegacyXmlReportGeneratingListener` writes `@message` only when the throwable's message is non-null; it always writes `@type` as the throwable's class name. A bare `NullPointerException`, or an assertion helper invoked without a description, gives you `@type` and no `@message` at all. 2. **The message was empty rather than null.** Maven Surefire writes `@message` only when the message is non-null *and* non-empty, so an empty-string message is written as no attribute. 3. **The element is nil.** All three outcome elements are `nillable="true"`, so a writer with nothing recorded may emit an empty `<failure/>` -- no attributes, no text, nothing to render. 4. **The consumer read a different element.** A reader that takes the first outcome element on a case takes that element's `@message`; if the case carries more than one, the message shown belongs to whichever came first, and may simply be absent on that one. Allure 2's `JunitXmlPlugin` shows the pattern clearly: it takes the first of `<failure>`, `<error>` and `<skipped>` that is present, copies the element's `@message` into the result's message field and the element's **text content** into the trace. No `@message` means no message -- the reader does not go fishing in the trace for one. ## The two writers do not compute `@type` the same way This is the detail that surprises people who try to use `@type` as a class name. - **JUnit 5's legacy listener** writes the throwable's class name directly, read from a real class object. - **Maven Surefire** derives `@type` from the **stack-trace text**. When the throwable has a message, it takes the substring before the first colon; when it does not, it takes the first whitespace-delimited token of the trace. Most of the time both produce the same-looking string, because a Java stack trace conventionally starts with the fully-qualified exception name followed by a colon. But one is a class name and the other is the leading fragment of a formatted string, and they diverge whenever the trace does not start the way the heuristic assumes -- a trace that has been shortened, a message containing an early colon, a trace produced by something other than a JVM throwable. ## What to do about it - **Treat `@message` and `@type` as optional.** Code that dereferences either without a null check will fall over on ordinary, valid files. - **Fall back to the text content.** When `@message` is absent, the trace's first line normally repeats the exception name and any message. That is a heuristic, not a guarantee -- the writer may have shortened the trace, and a nil element has no text at all. - **Do not parse `@type` as a class name.** Match it as a string, or accept that a Surefire-written file may hand you something that is not one. - **Fix it at the source where you can.** Assertion messages that are never null cost nothing and make every downstream reader useful; a case that fails on a bare `NullPointerException` is telling you as much about the test's diagnostics as about the product. - **Distinguish "no message" from "no failure".** An outcome element with no `@message` is still a red case. Readers that treat a missing message as a missing outcome turn red into green.
- Both writers fill `@type`. Do they compute it the same way?No. JUnit 5's legacy listener writes the throwable's class name directly. Maven Surefire derives `@type` from the stack-trace text -- the substring before the first colon, or the first whitespace-delimited token when there is no message. Usually they look identical, but one is a class name and the other is a parsed fragment of a formatted string.
- If `@message` is missing, where can a reader recover something to show?From the element's text content: a Java stack trace's first line normally repeats the exception name and its message. Treat that as a heuristic, not a contract -- the trace may have been shortened before writing, and a nil outcome element has no text at all.
saying these in an interview costs you the question
- Says the stack trace lives in a <stackTrace> child of <failure>
- Claims @message and @type are required attributes
- Treats @type as guaranteed to be a real class name
- Blames the CI tool when the throwable simply had no message
- Reads a missing @message as a missing failure