Why does a parameter that fails type conversion produce a different error shape than failures raised inside the handler, and how do you unify them?
answer
- binding runs before the handler
- handler-scoped mappers never see it
- register at framework scope
- one envelope for every client error
- never reflect raw input unescaped
basics
~20 sConversion runs in the binder, upstream of the handler, so the framework's own fallback answers instead of the team's error mapping. Unify them by mapping binding failures at framework scope into the same envelope as every other client error.
solid answer
~50 sHandler-scoped error handling can only see failures raised *after* the handler is entered, and a conversion failure aborts before that. What answers instead is the framework's built-in fallback — often an HTML page, a bare status line, or a body shaped by the framework rather than by the service. The fix is to register the binding-failure case in the **framework-wide error mapping**, the same layer that renders the service's other client errors, and to emit the same envelope: a stable machine-readable code, the **parameter name**, and the expected form. Two things to keep out of that body: the raw offending text reflected back into a response that might be rendered as markup, and internal type names or stack traces. Keep the status a client error — the request is fixable by the caller — rather than letting an escaped exception become a server error.
go deeper
Understand that a badly typed parameter is answered by the framework itself, before your code runs, which is why the error looks nothing like the ones your handler produces.
Explain the ordering that causes it and where the mapping has to be registered instead, plus what belongs in the body: a stable code, the parameter name, the expected form.
Show the production consequences — retry storms from a mistaken server error, silent wrong answers from swallowed failures, caller-controlled text in responses and logs.
Own the error contract across services: one envelope, one place it is defined, tests that assert it, and a rule that no framework default is allowed to reach a client.
## Where the failure happens decides what answers it The request pipeline reaches a handler in stages: match a route, extract raw values, convert them to the declared types, invoke the handler. A failure in the third stage happens while the framework is still assembling the call, so anything registered *around the handler* — a try block in the body, an error mapper scoped to that handler or controller — is not in the picture yet. The framework falls back to whatever it does with an unhandled binding problem. That fallback is rarely what a service wants to publish. Depending on the framework and its configuration it can be an HTML error page, an empty body with a status line, or a structured body in the framework's own vocabulary. Meanwhile the service's own failures — the ones raised inside handlers — go through the team's mapper and come out in the house envelope. The result is one API with two error shapes, split along a line no client can see or predict. ## Symptoms that point here - One endpoint returns the house JSON error for a rejected business rule and markup for a mistyped number. - The error body names an internal type, or carries a stack trace, for exactly the malformed-parameter case. - Client code that parses the standard error envelope throws on the response instead of reporting it. - The service's server-error rate rises with a caller's bad deployment, because binding failures are being counted as server faults. ## Unifying the surface 1. Find where the framework reports **binding and conversion failures** — a dedicated failure type, a hook, or a configurable handler on the binder — and register the service's mapper for it at application scope, not per route. 2. Emit the **same envelope** as every other client error: a stable code, a human-readable message, and a place for per-field detail. 3. Populate the detail with the **parameter name** and the **expected form** ("whole number", "one of: red, blue"). That is what turns a dead end into a fixable request. 4. Keep the status in the client-error family. The caller sent text the declared contract does not accept, and no retry of the same request will succeed. 5. Add one test per shape — a malformed scalar, a bad element in a list, an unknown enumerated member — asserting the envelope, not just the status. ## What not to put in the body | Include | Leave out | |---|---| | A stable error code clients can branch on | Internal type or class names | | The parameter name that failed | A stack trace or framework-internal message | | The expected form, or the allowed values | The raw offending text reflected into a markup response | | A correlation identifier for support | Anything derived from other users' requests | Reflecting the raw value back deserves its own note. It is attacker-controlled text going straight into a response, and into logs. In a body that is rendered as markup it is an injection vector; in a log line it can forge fields or break the parser downstream. If the value is genuinely useful for diagnosis, truncate it, escape it for the format it lands in, and prefer reporting its *shape* — length, first characters — over the whole thing. ## Two failure modes worse than an ugly page **Turning it into a server error.** An exception escaping the binder into the generic catch-all produces a `5xx`. The caller's client library retries, because that is what `5xx` means; the retries also fail; the service's alarms fire for someone else's typo. The failure is deterministic and caller-fixable, and the status has to say so. **Swallowing it.** The opposite instinct is a catch-all around binding that substitutes a neutral value and continues. Now a malformed page cursor returns page one, a malformed filter returns everything, and a caller integrating against the API has no signal that anything is wrong. A binding failure should never turn into a successful response. ## Consistency is the actual deliverable Clients are written against the shape of errors as much as against the shape of successes. A service whose malformed-parameter answer looks like its unauthorised answer and its rejected-rule answer can be consumed by one piece of client code, documented once, and monitored with one query. That uniformity is worth the small amount of framework-specific wiring it takes to route binder failures into the same renderer as everything else.
- Why does a conversion failure often escape a team's error mapper entirely?Because the mapper is registered around handler execution, and conversion happens while the call's arguments are still being assembled. The framework answers with its own fallback instead. Registering the same renderer for the binder's failure type, at application scope, is what puts those responses back in the house shape.
- Is it safe to include the value that failed to convert in the error response?Only carefully. It is caller-controlled text landing in a response body and in logs, so it must be escaped for wherever it goes and truncated to a sane length. Naming the parameter and the expected form is usually more useful to the caller anyway, and carries no injection risk.
- What is wrong with catching binding failures and continuing with a safe default?It converts a detectable client bug into a wrong answer. A malformed cursor silently returns the first page; a malformed filter silently returns everything. The caller sees a success and integrates against behaviour nobody designed, and the mistake surfaces much later as bad data rather than a failed request.
saying these in an interview costs you the question
- Mapping a conversion failure to a server error because an exception was thrown
- Registering the error mapping only inside the handler that never runs
- Reflecting the raw offending value into a response rendered as markup
- Swallowing a binding failure and continuing with a substituted value
- Returning an internal type name as the client-facing error message