Quick answer
Spring Boot exception handling should convert known failures into a stable, safe HTTP contract at the boundary that can still control the response. For MVC controller execution, @RestControllerAdvice and @ExceptionHandler can map validation failures, missing resources, conflicts, and selected application exceptions to ProblemDetail. ProblemDetail is Spring’s representation for RFC 9457-style problem documents, which standardize a status, type, title, detail, instance, and safe extension members.
Centralized MVC advice is useful but intentionally not universal. It does not automatically own every error from a servlet filter, Spring Security authentication or authorization path, asynchronous execution, a background scheduler, a message listener, or a response that has already been committed to the client. Each of those paths needs its own boundary-aware strategy. Trying to send a second JSON error after bytes or headers have been committed creates misleading logs and corrupted responses.
This article uses Spring Boot 4.1 as the documented mainline and Java 17 examples. The high-level error contract works for Spring Boot 3.5 too, but teams should verify their exact Spring Framework and Spring Security behavior for ProblemDetail, error dispatch, authentication entry points, and async exception handling. Error behavior is part of a public API; upgrade testing should include it rather than assuming an annotation makes versions interchangeable.
The security rule is simple: clients need enough information to correct a request or understand a retry decision, not exception class names, SQL text, stack traces, access-control structure, tokens, addresses, or personal data. Detailed diagnostics belong in protected logs and traces with a correlation ID, safe retention, and access controls.
Shared order-service incident stage
During the flash sale, a request can fail at many stages. A malformed order body should become a predictable client problem before the application service starts. A customer who is not authenticated may be stopped by Spring Security before MVC reaches the controller. An authenticated customer attempting to act on another tenant’s order should receive the authorization policy’s response, not a database clue about that order. A valid order may conflict when its idempotency key is already in use or inventory has been reserved by a concurrent transaction.
The inventory pool then becomes slow. A request may time out before a connection is obtained, a database exception may occur during a transaction, or a remote payment call may fail after local order state has already committed. These outcomes are not interchangeable. The API must avoid claiming a failed transaction when an order was actually persisted, and it must avoid returning a successful payment state when the downstream result is unknown. The state model and idempotency record are more reliable than the client’s timed-out socket.
Finally, the transport may have started the response. A streaming endpoint, large serialization response, or a filter that writes headers can commit HTTP output before a later exception. At that point an MVC advice handler cannot safely replace the body with an RFC 9457 document. The correct response is usually to stop work, record protected diagnostic evidence, and let connection semantics or the already-started response stand. Design endpoints so important order commands complete their decision before response streaming begins.
The incident timeline teaches responders to classify failures by boundary: malformed input, authentication, authorization, application conflict, persistence failure, dependency unavailability, asynchronous failure, and committed-response failure. That classification produces safer client semantics and much better operational dashboards than one catch-all “order service error.”
Core mechanism and evidence boundary
RFC 9457 defines a problem-details format for HTTP APIs. A problem’s type should be a stable URI that identifies the category; title is a short summary; status represents the HTTP status; detail is a human-readable explanation appropriate for the client; and instance can identify a particular occurrence or support path. Extension members are allowed, but they need a compatibility policy. An API might expose a safe code, a correlation identifier, and a list of field errors, while keeping internal exception names and storage details private.
Map exceptions by meaning, not by convenience. A DTO validation failure is a client input problem. A domain InventoryUnavailableException can map to a conflict or another documented business outcome if the API contract says so. A duplicate idempotency key may map to the original representation or a conflict depending on the command semantics. An unexpected database outage is a server-side condition, but even then the client should see a general message and a correlation ID rather than a vendor error string.
@RestControllerAdvice is an MVC mechanism. It can handle exceptions propagated through controller invocation and compatible MVC resolution. It does not catch every error in a process. A servlet filter runs around the MVC dispatcher; if it throws before delegating or after response commitment, it needs local handling or must rethrow to a container-level error mechanism. Spring Security’s AuthenticationEntryPoint and AccessDeniedHandler own key authentication and authorization response paths. A @Async method, scheduled task, or message listener has no request response to convert; it needs structured logging, retry or dead-letter policy, and an observable state transition.
Async MVC requires special care. When a controller returns a Callable, DeferredResult, or another asynchronous result, the framework’s handling depends on where and when an exception completes. Test the exact selected mechanism. Do not assume advice has the same context, security state, transaction, or response mutability as the original thread. Preserve an explicit correlation context only through supported instrumentation and never by copying credentials into arbitrary worker state.
Measure failures with bounded, decision-level dimensions: endpoint template, status family, stable problem type, authentication outcome, authorization outcome, and dependency category. Do not label metrics with detail, exception message, order ID, email, raw URL, stack trace, SQL, or token. Protected structured logs can record the exception class, causal chain, correlation ID, and safely redacted context. A trace sampled from one failure explains one path; it cannot prove the whole error rate.
Minimal reproducible Spring example
The MVC advice below handles two defined application outcomes and leaves framework-owned security and filter paths to their appropriate components. It makes the public values stable and keeps implementation details out of the response. In Spring Boot 4.1, confirm the exact framework version’s ProblemDetail API; Spring Boot 3.5 users should test the same contract against their supported framework line.
package com.example.orders;
import java.net.URI;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public final class OrderProblemAdvice {
@ExceptionHandler(InventoryUnavailableException.class)
ProblemDetail inventoryUnavailable(InventoryUnavailableException exception) {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.CONFLICT,
"The requested quantity is no longer available.");
problem.setType(URI.create("https://api.example.com/problems/inventory-unavailable"));
problem.setTitle("Inventory unavailable");
problem.setProperty("code", "inventory_unavailable");
return problem;
}
@ExceptionHandler(InvalidOrderStateException.class)
ProblemDetail invalidOrderState(InvalidOrderStateException exception) {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.CONFLICT,
"The order cannot transition from its current state.");
problem.setType(URI.create("https://api.example.com/problems/invalid-order-state"));
problem.setTitle("Order state conflict");
problem.setProperty("code", "invalid_order_state");
return problem;
}
}
The advice should not be presented as an all-purpose safety net. It can receive exceptions produced while MVC can still form a response; it cannot undo a committed response and should not be forced to impersonate Spring Security. Configure an authentication entry point to produce an intentional unauthenticated response, configure an access-denied handler for forbidden actions, and keep those response shapes consistent with the public problem contract where the chosen security integration allows it.
For validation failures, map framework validation exceptions to a stable problem type and a safe list of field errors. Include a field name or JSON Pointer and a rule code only when it does not expose hidden fields or business policy. For example, items[2].quantity and must_be_positive can be useful; an SQL constraint name, rejected card number, full raw JSON body, or an unredacted exception.getMessage() is not. Put a correlation ID in an extension only if it is opaque and clients can safely cite it to support.
Failure modes and dangerous misconceptions
“@RestControllerAdvice catches every error.” It handles a useful MVC subset, not all process failures. Filters, security handlers, async execution, background jobs, container errors, and committed responses have different lifecycle rules. Write tests around each boundary that your service owns and route unowned failures to an explicit platform policy.
“A 500 response proves no state changed.” A database transaction may have committed before response serialization fails; an external payment may have accepted a request before the caller times out. Use durable order states, idempotency keys, reconciliation, and a status resource to discover the outcome. Never retry a non-idempotent command solely because the HTTP response was lost.
“Expose the exception message to speed support.” Exception messages frequently contain SQL, hostnames, field values, internal identifiers, and library behavior. They can mislead clients and aid attackers. Publish a stable type and safe detail; keep full diagnostics behind access controls linked by a correlation ID.
“Return an error after the response is committed.” HTTP headers and some body bytes may already be on the wire. A second JSON document can corrupt a stream and make the client parse neither outcome. Check whether the response is committed, avoid late writes in filters, stop the work safely, and record the failure. For command endpoints, structure work so the durable decision precedes response writing.
“All failures should be retried.” Validation and authorization errors are not transient. A conflict may need user action or a new representation. A dependency timeout might be retryable only within an end-to-end budget and with an idempotency guarantee. Classify retryability as part of the API and worker contract, not as an automatic response to any exception.
Security, privacy, transaction, capacity, and cost implications
Error documents are public data surfaces. Use stable public type URIs controlled by the API owner; avoid type names that reveal internal packages or infrastructure. Scrub logs and responses of credentials, session IDs, authorization headers, addresses, payment data, and customer identifiers. Restrict access to correlation logs, set retention deliberately, and ensure error reporting tooling honors the same redaction policy.
Authentication and authorization failures deserve cautious wording. A service may choose 401 for missing or invalid authentication and 403 for authenticated but forbidden action, but must avoid leaking whether a protected object exists when that fact is sensitive. Let the security policy and threat model decide whether a missing-or-forbidden resource should be indistinguishable. Do not query more data merely to make an error message friendlier.
Exception boundaries interact with transactions. Translate a known domain outcome outside or at the edge of the transaction without swallowing a persistence problem that should trigger rollback. A generic catch inside a transactional service can accidentally convert a failure into a return value and allow the transaction to commit. Be explicit about which exceptions represent expected business outcomes and which mean the command cannot safely continue.
Verbose exception capture also has capacity and cost consequences. Stack traces for every expected validation error create noise, storage cost, and alert fatigue. Count expected failures at low cardinality and sample protected diagnostics appropriately. Alert on user-impacting rates, sustained dependency failures, and recovery conditions rather than paging on every malformed request during a client rollout.
Testing and production validation
Use MVC tests to assert the media type, status, type, title, safe detail, and allowed extension members for validation, inventory conflict, and invalid order state. Assert that sensitive values and exception class names are absent. Test that a malformed request invokes neither the application transaction nor remote work. Test success and error representations against client contract fixtures so small copy changes do not silently break integration.
Separately test Spring Security authentication and authorization paths with the configured filter chain. Test a filter that fails before dispatcher invocation and one that encounters a committed response; assert the chosen logging and connection behavior instead of expecting MVC advice to replace output. For asynchronous controllers and workers, test how exceptions are captured, how correlation context is propagated by supported tooling, and how retry or dead-letter policy prevents hot loops.
For the flash sale, inject inventory conflicts, database pool timeouts, and a response-disconnect simulation around a committed order. Verify that idempotency and order lookup reveal the durable state, that dashboards distinguish client, conflict, auth, and server outcomes, and that no problem response contains an order ID, address, token, SQL fragment, or stack trace. Production verification should include a canary request plus aggregate error-rate, pool-wait, and log-redaction checks.
Operations checklist
- Define stable RFC 9457 problem types and safe extension members for public error classes.
- Use
ProblemDetailand MVC advice for controller-path exceptions that can still control the response. - Keep authentication and authorization responses owned by the configured security boundary.
- Treat servlet filters, async work, message consumers, and scheduled jobs as separate error paths.
- Never write a replacement problem document after the response is committed.
- Return an opaque correlation ID, not exception messages, stack traces, or internal identifiers.
- Classify validation, authentication, authorization, conflict, and dependency failures separately.
- Preserve idempotency and durable order status for ambiguous timeout or disconnect outcomes.
- Test public error contracts and redaction as deliberately as successful JSON responses.
- Alert on aggregate user impact and inspect protected diagnostics for individual causes.
Official sources
- RFC 9457: Problem Details for HTTP APIs — accessed 2026-08-05.
- Spring Framework error responses — accessed 2026-08-05.
- Spring Security exception handling — accessed 2026-08-05.
- Spring Framework asynchronous MVC requests — accessed 2026-08-05.
Continue the learning path
Ground error decisions in Production Spring Boot Backend Systems Explained and reject malformed input in Spring Boot Request Validation Explained. Continue with the Spring Backend course, discover related Topics, and use Spring Backend Engineering for the full sequence.