Back to results for “Exception Handling”
.NET › Global Exception Handling & Middleware

How should a global exception handler differentiate between exceptions that should return specific client-facing details versus exceptions that should always show a generic message?

Intermediate

Answer

A common pattern defines a custom base exception class (like ApiException with a public Message and StatusCode meant for client display) that business logic throws for expected, user-facing error conditions, while any exception NOT inheriting from this base type (unexpected bugs, infrastructure failures) is treated as an internal error and given only a generic message -- this explicit opt-in design prevents accidentally leaking internal exception details for the many exception types never designed to be shown to end users.
public class ApiException : Exception {
    public int StatusCode { get; }
    public ApiException(string message, int statusCode = 400) : base(message) => StatusCode = statusCode;
}

// In the handler:
var (status, message) = exception is ApiException apiEx
    ? (apiEx.StatusCode, apiEx.Message)  // safe, intentionally client-facing
    : (500, "An unexpected error occurred");  // generic for anything else
Real-world example A checkout flow throws a custom InsufficientStockException : ApiException with a clear customer-facing message, while an unrelated database timeout exception (not inheriting ApiException) is automatically shown only as a generic 'something went wrong' message, preventing internal details from leaking.

Common follow-ups: How do you ensure developers consistently use the ApiException base class for new user-facing errors?;What's the risk of accidentally making ApiException too broadly applicable?