Custom Exceptions
Custom exceptions express domain failures — InsufficientFundsException, PatientNotFoundException — enabling callers to catch precisely without string matching.
Introduction
Custom exceptions express domain failures — InsufficientFundsException, PatientNotFoundException — enabling callers to catch precisely without string matching. Inherit from Exception or specialized bases like InvalidOperationException when appropriate.
Include message, inner exception, and optional custom properties (ErrorCode). Serializable attribute legacy for remoting. Interviewers ask when custom vs standard exceptions and designing exception hierarchies without explosion.
The story
When a customer tries to withdraw more than their checking account balance, the core banking service throws InsufficientFundsException with structured properties — account ID, requested amount, and available balance — so the API middleware can return a precise 422 response and log actionable details without parsing error message strings.
Understanding the topic
Key concepts
- Derive from Exception or ApplicationException (avoid latter).
- Three constructors: default, message, message+inner.
- Custom properties for ErrorCode, EntityId.
- Sealed unless extensible hierarchy intended.
- Document which methods throw which types.
- Map to ProblemDetails extensions in APIs.
Step-by-step explanation
- throw new DomainException('reason', inner);
- Catch filters on type or ErrorCode property.
- Middleware inspects exception type → HTTP status.
- Unit tests assert Throws
from xUnit. - Avoid deep inheritance — flat + ErrorCode often enough.
- Localization via message resources at catch boundary.
Practical code example
Domain exception with error code for API mapping:
namespace TechLearningPro.CustomExceptions;public sealed class InsufficientFundsException : Exception{public Guid AccountId { get; }public decimal Requested { get; }public decimal Available { get; }public InsufficientFundsException(Guid accountId, decimal requested, decimal available): base(quot;Account {accountId} cannot withdraw {requested:C}; available {available:C}."){AccountId = accountId;Requested = requested;Available = available;}}public static class WithdrawalGuard{public static void EnsureFunds(decimal balance, decimal amount, Guid accountId){if (amount > balance)throw new InsufficientFundsException(accountId, amount, balance);}}
Line-by-line code explanation
sealed class InsufficientFundsException : Exceptiondefines a typed domain failure.public Guid AccountId { get; }carries the account identifier for structured logging.public decimal Requested { get; }andAvailable { get; }expose the amounts involved.: base($"Account {accountId} cannot withdraw...")sets a human-readable message for operators.AccountId = accountIdassigns custom properties in the constructor body.WithdrawalGuard.EnsureFunds(...)centralizes the balance check before debiting.if (amount > balance) throw new InsufficientFundsException(...)throws only for genuine overdraft attempts.throw newstops execution immediately — callers must catch or let middleware handle it.
Key takeaway: Rich properties enable structured logging and ProblemDetails extensions without parsing Message.
Real-world use
Where you'll use this in production
- Banking overdraft and limit violations.
- Healthcare contraindication alerts.
- Multi-tenant authorization failures.
- Inventory shortfall on order placement.
Best practices
- Name with Exception suffix.
- Provide meaningful messages for operators.
- Include inner exception when wrapping.
- Map each domain exception to HTTP status once in middleware.
- Don't create exception per HTTP status only — use ProblemDetails.
Common mistakes
- Inheriting ApplicationException without reason.
- Too many exception types differing only by message.
- Serializable boilerplate on modern apps unnecessarily.
- Throwing custom for non-exceptional validation.
Advanced interview questions
Q1BeginnerWhen create custom exception?
Q2BeginnerRequired constructors?
Q3IntermediateException hierarchy depth?
Q4IntermediateTest custom exceptions?
Q5AdvancedDesign exception strategy for microservices.
Summary
Custom exceptions model typed domain failures. Add properties for structured error handling. Map to HTTP centrally in middleware. Keep hierarchy shallow and purposeful. Next: file I/O operations.