3.8 KiB
Client-Safe Error Boundary Design
Date: 2026-08-02
Status: approved by the user's instruction to apply the detailed P1/P2 review sequentially
Scope: HTTP error envelopes in adapter:inbound:web and the sample-portfolio domain advice
Context
Several handlers pass Exception#getMessage(), rejected request values, or a raw request URL into
the public error envelope. Those values are not a stable API contract and can contain identifiers,
tokens, uploaded values, configuration details, or internal diagnostics. Persistence and outbound
dependency failures already use fixed client-safe messages; the rest of the HTTP boundary must
follow the same rule.
Decision
The inbound adapter owns a message allowlist keyed by stable error code. Handlers may expose only:
- stable
code,category, HTTP status, andretryablefromApiErrorCode; - fixed, code-specific client messages;
- bounded structural details such as field name, validation reason code, expected Java type, supported HTTP methods, or supported media types.
They must not expose exception messages, rejected values, raw request URLs, adapter/configuration diagnostics, opaque cursors, authentication diagnostics, resource identifiers, or duplicate domain values. Bean Validation interpolated/default messages are also discarded because custom templates can include the validated value. Validation details contain only normalized server-owned property names plus allowlisted reason codes and fixed messages; collection/map keys and indices are removed.
ClientSafeErrorMessages is extended for skeleton-wide operational codes. The sample keeps its
domain wording in a separate package-private PortfolioClientSafeErrorMessages, preserving the
rule that production modules do not know sample business concepts.
Public Messages
Representative mappings are fixed as follows:
MAPPING_FAILED→Request data could not be mapped;BAD_PARAMETER→Request parameter is invalid;INVALID_TOKEN→Authentication token is invalid;UNAUTHENTICATED→Authentication is required;- authorization denials →
Access is denied; PRECONDITION_FAILED→Resource state changed; refresh and retry;- page/cursor failures → generic corrective text, with safe field/reason details retained;
ADAPTER_DISABLEDand internal classifications →Internal server error;- domain not-found/conflict/invariant codes → fixed noun-level text with no ID/title value.
Transport overrides use fixed wording and retain only safe protocol metadata. For example, 405
still emits Allow, while both controller-route (NoHandlerFoundException) and static-resource
(NoResourceFoundException) 404s use the same envelope without echoing the request URL.
Testing
Tests inject conspicuous secret sentinels into exception messages, rejected values, URLs, tokens,
IDs, and duplicate titles. Every resulting response must preserve its status/code/category while
excluding the sentinel from both error.message and error.details.
Validation tests additionally place sentinels in interpolated/default messages and iterable
keys/indices. A real MockMvc resource-resolution request verifies the Spring 7
NoResourceFoundException path rather than calling the advice method directly.
The focused module suites remain the primary verification:
:adapter:inbound:web:testfor operational and transport handlers;:sample-portfolio:testfor domain advice and sample wire behavior;verifyCleanArchitectureDependenciesfor dependency direction.
Non-Goals
- no change to error codes, categories, statuses, or retryability;
- no suppression of server-side logs or tracing in this batch;
- no application/domain dependency on HTTP response types;
- no generic exception-message sanitizer based on regexes or truncation;
- no staging, commit, amend, or push by an agent.