skip to content

When the shared failure factory is handed a thrown exception, what should it put in the client-facing message field?

level: seniorimportance: must knowfreq 52%

answer

  1. classification input, not a text source
  2. render the message from the code
  3. default branch is one generic message
  4. detail to the log, keyed by correlation id
  5. one wiring point makes the rule enforceable

basics

~20 s

Text chosen from the mapped error code, not the exception's own message. The factory should default to a generic message for anything unrecognised, and send the exception text and stack to the log record keyed by the same correlation id.

solid answer

~50 s

Treat the factory as default-deny: the exception object is an input to *classification*, never a source of client-facing text. A recognised failure is rendered from a curated message attached to its error code; anything unrecognised gets one generic message, the same for every such failure. The raw message, cause chain and stack trace go to the log record, tied to the correlation id already in the body, so support can reach the detail through the id rather than through the payload. The structural point is that this is only enforceable because there is one write path - if the field is never wired to the exception's text at that single point, no handler can wire it anywhere else. Copying `exception.message` into the body is the default that quietly ships file paths, query fragments and internal type names.

go deeper

for a junior

Hold the rule in one line: the message a client sees comes from the error code, not from the exception. The exception's own text belongs in the log, not in the response.

for a middle

Explain the mechanism: classify the failure to a code, look the message up from that code, default anything unrecognised to one generic code, and send the raw text and stack to the log record.

for a senior

Demonstrate default-deny plus the operational half - the correlation id is the link that makes a stripped body debuggable, and a single test on an unmapped failure with distinctive text proves the leak path is closed.

for a principal

Own the policy question: which detail is part of the published contract and which is support-only, who reviews a new code's message text, and whether any environment is permitted to widen responses at all.

The shared factory is the last piece of code that touches a failure before it becomes bytes on the wire. That position makes it the only place where the question 'what does the client get to see?' has a single answer, and the answer that scales is **default-deny**: the body contains what you deliberately put there, and the thrown object contributes classification only. ## Two sources of text, only one of them yours | Source | Who wrote it | Safe as client-facing text | |---|---|---| | A message attached to your error code | You, for this audience | Yes | | A message on a domain failure your code raised | You, but review the audience | Usually, if written for callers | | The message on a framework or runtime exception | The framework's authors, for developers | No | | The cause chain and stack trace | The runtime | No | The middle row is where teams get sloppy. A domain failure whose message reads *user 4471 has no active subscription in tenant 88* was written for a log line, not for a stranger. Owning the text does not by itself make it audience-appropriate; the factory should render from the code, and any text meant for the caller should be authored as part of the code's contract. ## The mechanism, step by step 1. **Classify.** The factory receives the failure and resolves it to one of your error codes. Recognised failures map to their code; anything not recognised maps to one generic code. 2. **Render from the code.** The client-facing message is looked up for that code. It is stable text, reviewed like any other part of the contract, and it does not vary with the incident. 3. **Attach declared detail only.** If a failure carries structured detail the caller genuinely needs, it travels in a declared place in the envelope with a declared shape - not as prose interpolated into the message. 4. **Log the rest.** The exception's own message, its cause chain and its stack go into the log record for this request, carrying the correlation id that the body also carries. That is the link that makes the stripped body workable rather than merely safe. 5. **Default branch is generic.** An unrecognised failure produces the contract shape with the generic code and message. This is the branch that fires for the failures nobody anticipated, which is exactly when the exception text is most likely to be revealing. ## Why the single write path is what makes it enforceable A rule that says 'do not put exception text in responses' is unenforceable across a hundred handlers. A factory whose message field is simply **never wired to the exception's text** is enforceable by construction: there is one function, it has one expression producing the message, and that expression reads a code-to-message lookup. Reviewing this is reading one function. Testing it is provoking an unmapped failure with a deliberately loud message and asserting that the message does not appear in the body. The same argument applies to everything else the payload might leak - internal identifiers, the name of the component that failed, the count of rows a query returned. If it is not assembled at the single write point, it cannot be in the body. ## The support workflow that replaces the detail The objection to a generic body is always the same: *now nobody can debug it*. The answer is that the detail did not disappear, it moved. The client holds the correlation id; the log record holds the exception text, the stack, the request attributes and the timing, indexed by that id. A support request becomes 'quote the id', and an engineer resolves it in one query. That workflow is what buys you the right to strip the body, and it is worth stating explicitly in an interview, because a candidate who strips the body without building the lookup path has made the system harder to operate rather than safer. ## Edges worth knowing - **Validation detail is not exception text.** Structured information about which inputs were unacceptable is authored content for the caller and belongs in the declared place in the envelope; it does not arrive by copying a thrown object's prose. - **Environment switches are a trap.** A mode that includes stack traces in responses is only as good as the confidence that it is off in every environment that faces a real caller, and that confidence is a deployment property, not a code property. - **A generic message per code beats one per incident.** Resist the urge to make the message helpful by interpolating the specifics; the specifics are the thing you are choosing not to send, and the correlation id is how the caller gets them. - **The unrecognised branch still needs the full contract.** Code, message, correlation id: a generic failure is not an excuse to emit a shape the client cannot parse.

  • Is it acceptable to use a domain failure's own message as the client-facing text?
    Only when that text was written for callers, and most is not - domain messages routinely name internal identifiers, tenants or rows because they were written for log lines. The safer construction is to render from the error code and treat any caller-facing text as part of the code's published contract, reviewed like the rest of the API surface.
  • If the body carries only a code and a generic message, how does anyone debug a reported failure?
    Through the correlation id. The same id in the body is on the log record that holds the exception message, cause chain, stack and request attributes, so a support ticket that quotes the id resolves in one query. Building that lookup path is the precondition for stripping the body; without it you have made the system harder to operate.
  • What single test proves the factory does not leak an unmapped exception's text?
    Provoke a failure that no mapping recognises, carrying a deliberately distinctive message, and assert that the string does not appear anywhere in the serialized response while the generic code and the correlation id do. Pair it with a log assertion that the distinctive text was recorded, so the test also proves the detail was preserved rather than lost.

saying these in an interview costs you the question

  • Copies the thrown exception's message straight into the response body
  • Assumes any message the team wrote is safe for callers to read
  • Strips the body but never builds the correlation-id-to-log lookup
  • Interpolates incident specifics into the message to be helpful
  • Relies on a detail switch being off in every caller-facing environment
  • Emits a bare generic string without the contract fields for unmapped failures