Loading learning content...
Names are interfaces. When a developer sees throw new UserSuspendedDueToFraudException(userId) in a stack trace, they immediately understand what happened without reading any documentation. When an operator sees error code PAYMENT.CAPTURE.GATEWAY_TIMEOUT in a dashboard, they know exactly which system is failing.
Poor naming forces investigation that good naming eliminates. This page establishes conventions for naming exception classes, error codes, and messages that make your error handling self-documenting, consistent, and operationally transparent.
By completing this page, you will master: (1) exception class naming patterns that reveal intent, (2) error code design for operational visibility, (3) message formatting for debugging efficiency, (4) consistency rules that scale across large codebases, and (5) practical naming examples across multiple scenarios.
Exception class names should be immediately comprehensible in a stack trace. A developer debugging at 3 AM should understand what went wrong from the class name alone, without needing to open source files.
InsufficientFundsException, not AddMoreFundsExceptionOrderNotFoundException not EntityNotFoundInDatabaseExceptionEmailAlreadyRegisteredException not DuplicateExceptionPaymentGatewayTimeoutException not just TimeoutExceptionGenericException — Tells nothingError1, Error2 — MeaninglessMyAppException — Which failure?BadDataException — What's bad about it?ProcessingException — What processing?InvalidException — Invalid what?FailedException — Everything failed!UserException — User did what?UserNotFoundException — Clear: user wasn't foundPaymentDeclinedException — Clear: payment rejectedInsufficientInventoryException — Clear: not enough stockEmailFormatInvalidException — Clear: email format wrongOrderAlreadyShippedException — Clear: can't modify shipped orderTokenExpiredException — Clear: auth token is staleRateLimitExceededException — Clear: too many requestsConcurrentModificationException — Clear: race conditionImagine seeing only the exception class name in a stack trace with no other context. Can you tell what happened? If not, the name needs work. Good names make stack traces readable narratives: 'at OrderService.submit() → OrderValidationException → PaymentService.charge() → InsufficientFundsException'.
Different exception categories follow different naming patterns. These patterns make exceptions predictable and discoverable across your codebase.
Pattern: {Entity}NotFoundException or {Entity}NotFoundError
12345678910111213141516171819202122
// Pattern: {Entity}NotFoundExceptionpublic class UserNotFoundException extends EntityNotFoundException { }public class OrderNotFoundException extends EntityNotFoundException { }public class ProductNotFoundException extends EntityNotFoundException { }public class InvoiceNotFoundException extends EntityNotFoundException { } // If lookup is by something other than ID, include it:public class UserByEmailNotFoundException extends UserNotFoundException { }public class OrderByReferenceNotFoundException extends OrderNotFoundException { } // Base class for the pattern:public abstract class EntityNotFoundException extends BusinessException { protected EntityNotFoundException(String message, String entityType, String id) { super(message, entityType.toUpperCase() + ".NOT_FOUND", Map.of("entityType", entityType, "entityId", id)); } @Override public int suggestedHttpStatus() { return 404; }}Pattern: {Entity}AlreadyExistsException or {Entity}Already{State}Exception
1234567891011121314151617181920212223242526
// Pattern: {Entity}AlreadyExistsExceptionpublic class UserAlreadyExistsException extends ConflictException { }public class EmailAlreadyRegisteredException extends ConflictException { }public class ProductSkuAlreadyExistsException extends ConflictException { } // Pattern: {Entity}Already{State}Exceptionpublic class OrderAlreadySubmittedException extends ConflictException { }public class OrderAlreadyCancelledException extends ConflictException { }public class PaymentAlreadyCaptured extends ConflictException { }public class ShipmentAlreadyDispatchedException extends ConflictException { } // Pattern: Concurrent modificationpublic class OrderConcurrentModificationException extends ConflictException { }public class OptimisticLockException extends ConflictException { } // Base class:public abstract class ConflictException extends BusinessException { protected ConflictException(String message, String errorCode) { super(message, errorCode); } @Override public int suggestedHttpStatus() { return 409; // Conflict }}Pattern: {Field/Entity}Validation{Issue}Exception or Invalid{Thing}Exception
12345678910111213141516171819
// Pattern: Invalid{Thing}Exception (simple cases)public class InvalidEmailFormatException extends ValidationException { }public class InvalidPhoneNumberException extends ValidationException { }public class InvalidDateRangeException extends ValidationException { }public class InvalidCredentialsException extends AuthenticationException { } // Pattern: {Field}{Issue}Exception (specific field issues)public class PasswordTooWeakException extends ValidationException { }public class UsernameTooShortException extends ValidationException { }public class AmountNegativeException extends ValidationException { } // Pattern: {Entity}ValidationException (aggregate of field errors)public class OrderValidationException extends ValidationException { }public class UserProfileValidationException extends ValidationException { } // Pattern: Missing required datapublic class MissingShippingAddressException extends ValidationException { }public class MissingPaymentMethodException extends ValidationException { }public class RequiredFieldMissingException extends ValidationException { }Pattern: {Rule}ViolationException or {Action}NotAllowedException
12345678910111213141516171819202122
// Pattern: Insufficient/Exceeded resourcepublic class InsufficientFundsException extends BusinessException { }public class InsufficientInventoryException extends BusinessException { }public class QuotaExceededException extends BusinessException { }public class RateLimitExceededException extends BusinessException { }public class StorageLimitExceededException extends BusinessException { } // Pattern: {Action}Not{Condition}Exception (can't do X because of Y)public class OrderNotCancellableException extends BusinessException { }public class UserNotActivatedException extends BusinessException { }public class SubscriptionNotActiveException extends BusinessException { }public class ItemNotRefundableException extends BusinessException { } // Pattern: {State/Condition}Exception (entity in wrong state)public class AccountSuspendedException extends BusinessException { }public class ProductDiscontinuedException extends BusinessException { }public class PromotionExpiredException extends BusinessException { } // Pattern: {Domain}{Rule}ViolationException (formal invariant)public class OrderMinimumNotMetException extends BusinessException { }public class MaximumQuantityExceededException extends BusinessException { }public class BusinessDayRequiredException extends BusinessException { }Pattern: {System}{Issue}Exception or {Operation}FailedException
123456789101112131415161718192021
// Pattern: {System}{Issue}Exceptionpublic class DatabaseConnectionException extends TechnicalException { }public class DatabaseTimeoutException extends TechnicalException { }public class CacheUnavailableException extends TechnicalException { }public class MessageBrokerException extends TechnicalException { } // Pattern: {ExternalService}Exceptionpublic class PaymentGatewayException extends TechnicalException { }public class EmailServiceException extends TechnicalException { }public class StorageServiceException extends TechnicalException { }public class SearchIndexException extends TechnicalException { } // Pattern: {ExternalService}{Specific}Exceptionpublic class PaymentGatewayTimeoutException extends PaymentGatewayException { }public class PaymentGatewayUnavailableException extends PaymentGatewayException { }public class StripeApiException extends PaymentGatewayException { } // Pattern: {Operation}FailedException (when operation name is clearer)public class SerializationFailedException extends TechnicalException { }public class EncryptionFailedException extends TechnicalException { }public class FileUploadFailedException extends TechnicalException { }Error codes are machine-readable identifiers that support monitoring, alerting, documentation, and client-side handling. Good error codes are hierarchical, consistent, and stable across releases.
Recommended Pattern: {DOMAIN}.{CATEGORY}.{SPECIFIC}
| Component | Purpose | Examples |
|---|---|---|
| Domain | Which business domain or system | ORDER, USER, PAYMENT, AUTH |
| Category | Type of failure within domain | VALIDATION, NOT_FOUND, CONFLICT, GATEWAY |
| Specific | Exact failure condition | AMOUNT_NEGATIVE, EMAIL_TAKEN, TIMEOUT |
1234567891011121314151617181920212223242526272829303132333435
# Error Code Hierarchy Examples ORDER.NOT_FOUND # Order doesn't existORDER.VALIDATION.ITEMS_EMPTY # Order has no itemsORDER.VALIDATION.AMOUNT_NEGATIVE # Invalid amountORDER.CONFLICT.ALREADY_SUBMITTED # Can't modify submitted orderORDER.CONFLICT.ALREADY_CANCELLED # Already cancelledORDER.INVENTORY.INSUFFICIENT # Not enough stockORDER.SHIPPING.ADDRESS_REQUIRED # Missing address USER.NOT_FOUND # User doesn't existUSER.VALIDATION.EMAIL_INVALID # Bad email formatUSER.VALIDATION.PASSWORD_WEAK # Password doesn't meet requirementsUSER.CONFLICT.EMAIL_TAKEN # Email already registeredUSER.STATE.SUSPENDED # Account suspendedUSER.STATE.NOT_VERIFIED # Email not verified PAYMENT.DECLINED # Generic declinePAYMENT.DECLINED.INSUFFICIENT_FUNDS # Specific: not enough moneyPAYMENT.DECLINED.CARD_EXPIRED # Specific: card expiredPAYMENT.DECLINED.FRAUD_SUSPECTED # Specific: fraud check failedPAYMENT.GATEWAY.TIMEOUT # Gateway didn't respondPAYMENT.GATEWAY.UNAVAILABLE # Gateway is downPAYMENT.CAPTURE.ALREADY_CAPTURED # Can't capture twice AUTH.INVALID_CREDENTIALS # Wrong username/passwordAUTH.TOKEN.EXPIRED # Token past expiryAUTH.TOKEN.INVALID # Token malformed/tamperedAUTH.PERMISSION.DENIED # Not authorizedAUTH.RATE_LIMITED # Too many attempts INFRA.DATABASE.CONNECTION_FAILED # Can't connect to DBINFRA.DATABASE.TIMEOUT # Query took too longINFRA.CACHE.UNAVAILABLE # Cache is downINFRA.MESSAGING.PUBLISH_FAILED # Message publish failedORDER.* matches all order errors in monitoringERROR_42 means nothing; ORDER.NOT_FOUND is self-documenting.NOT_FOUND for one entity, use it for all12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
/** * Centralized error code constants. * Provides type safety, discoverability, and documentation. */public final class ErrorCodes { private ErrorCodes() {} // Utility class // ========================================== // ORDER DOMAIN // ========================================== public static final class Order { private static final String PREFIX = "ORDER."; public static final String NOT_FOUND = PREFIX + "NOT_FOUND"; public static final class Validation { private static final String PREFIX = Order.PREFIX + "VALIDATION."; public static final String ITEMS_EMPTY = PREFIX + "ITEMS_EMPTY"; public static final String AMOUNT_NEGATIVE = PREFIX + "AMOUNT_NEGATIVE"; public static final String QUANTITY_INVALID = PREFIX + "QUANTITY_INVALID"; } public static final class Conflict { private static final String PREFIX = Order.PREFIX + "CONFLICT."; public static final String ALREADY_SUBMITTED = PREFIX + "ALREADY_SUBMITTED"; public static final String ALREADY_CANCELLED = PREFIX + "ALREADY_CANCELLED"; public static final String CONCURRENT_MODIFICATION = PREFIX + "CONCURRENT_MODIFICATION"; } public static final class Inventory { private static final String PREFIX = Order.PREFIX + "INVENTORY."; public static final String INSUFFICIENT = PREFIX + "INSUFFICIENT"; public static final String RESERVED = PREFIX + "RESERVED"; } } // ========================================== // USER DOMAIN // ========================================== public static final class User { private static final String PREFIX = "USER."; public static final String NOT_FOUND = PREFIX + "NOT_FOUND"; public static final class Validation { private static final String PREFIX = User.PREFIX + "VALIDATION."; public static final String EMAIL_INVALID = PREFIX + "EMAIL_INVALID"; public static final String PASSWORD_WEAK = PREFIX + "PASSWORD_WEAK"; } public static final class Conflict { private static final String PREFIX = User.PREFIX + "CONFLICT."; public static final String EMAIL_TAKEN = PREFIX + "EMAIL_TAKEN"; public static final String USERNAME_TAKEN = PREFIX + "USERNAME_TAKEN"; } } // Usage in exceptions: // throw new OrderNotFoundException(orderId, ErrorCodes.Order.NOT_FOUND); // throw new ValidationException(errors, ErrorCodes.Order.Validation.ITEMS_EMPTY);}Exception messages appear in logs, monitoring dashboards, and sometimes error responses. They should be consistently formatted, information-rich, and safe to display.
Build messages from exception data rather than accepting raw strings. This ensures consistency and prevents information omission.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
public class OrderNotFoundException extends OrderException { // ❌ BAD: Accept raw message (inconsistent, error-prone) public OrderNotFoundException(String message) { super(message); // Caller might forget order ID! } // ✅ GOOD: Build message from structured data public OrderNotFoundException(OrderId orderId) { super( buildMessage(orderId), // Consistent message ErrorCodes.Order.NOT_FOUND, orderId ); } // ✅ GOOD: Optional lookup context for richer messages public OrderNotFoundException(OrderId orderId, LookupContext context) { super( buildMessageWithContext(orderId, context), ErrorCodes.Order.NOT_FOUND, orderId ); } private static String buildMessage(OrderId orderId) { return format("Order not found: %s", orderId.value()); } private static String buildMessageWithContext(OrderId orderId, LookupContext ctx) { return format("Order %s not found (looked up by: %s, user: %s)", orderId.value(), ctx.lookupMethod(), ctx.requestingUserId() ); }} // =====================================================// MESSAGE TEMPLATES FOR CONSISTENCY// ===================================================== public final class MessageTemplates { // Entity not found messages public static String entityNotFound(String entityType, String id) { return format("%s not found: %s", entityType, id); } // Conflict messages public static String alreadyExists(String entityType, String field, String value) { return format("%s with %s '%s' already exists", entityType, field, value); } public static String alreadyInState(String entityType, String id, String state) { return format("%s %s is already %s", entityType, id, state); } // Validation messages public static String fieldInvalid(String field, String reason) { return format("Invalid value for '%s': %s", field, reason); } public static String fieldRequired(String field) { return format("Field '%s' is required", field); } // Insufficient resource messages public static String insufficientResource(String resource, Object required, Object available) { return format("Insufficient %s: required %s, available %s", resource, required, available); } // Technical failure messages public static String serviceUnavailable(String serviceName) { return format("Service unavailable: %s", serviceName); } public static String operationTimeout(String operation, Duration timeout) { return format("Operation '%s' timed out after %s", operation, timeout); }} // Usage:throw new UserNotFoundException(userId); // Uses: "User not found: {id}"throw new InsufficientFundsException( MessageTemplates.insufficientResource("funds", required, available), paymentId);Exception messages often end up in logs, error tracking systems, and sometimes client responses. NEVER include: passwords, API keys, tokens, full credit card numbers, SSNs, or other PII. When including identifiers, consider whether they're sensitive in your context.
In polyglot environments or large organizations, exception naming should be consistent across languages and services. Error codes especially must match, as they're used in API contracts and monitoring systems.
| Language | Class Suffix | Case Style | Example |
|---|---|---|---|
| Java | Exception | PascalCase | UserNotFoundException |
| C# | Exception | PascalCase | UserNotFoundException |
| Python | Error | PascalCase | UserNotFoundError |
| TypeScript | Error | PascalCase | UserNotFoundError |
| Go | N/A (errors are values) | camelCase | errUserNotFound |
| Rust | Error (in enum) | PascalCase | Error::UserNotFound |
12345678
// Java: UserNotFoundException with error code USER.NOT_FOUNDpublic class UserNotFoundException extends EntityNotFoundException { public UserNotFoundException(UserId userId) { super("User not found: " + userId.value(), "USER.NOT_FOUND", // Same error code across all services userId.value()); }}For organizations with multiple services, publish a naming standards document:
123456789101112131415161718192021222324252627282930313233343536
# Exception Naming Standards ## Error Code FormatAll error codes follow: `{DOMAIN}.{CATEGORY}.{SPECIFIC}` ### Reserved Domains- ORDER - Order processing- USER - User management - PAYMENT - Payment processing- AUTH - Authentication/authorization- INFRA - Infrastructure ### Standard Categories- NOT_FOUND - Entity doesn't exist (→ HTTP 404)- VALIDATION - Input validation failed (→ HTTP 400)- CONFLICT - State conflict (→ HTTP 409)- STATE - Invalid state transition (→ HTTP 422)- GATEWAY - External service issue (→ HTTP 503) ### Class Naming- Java/C#: `{Entity}{Issue}Exception`- Python/TS: `{Entity}{Issue}Error`- Follow domain's existing patterns ### Message Format- Format: "{Entity} {action/state}: {details}"- Include relevant IDs- No sensitive data- State facts, not actions ### Examples| Error Code | Java Class | Message ||------------|------------|---------|| USER.NOT_FOUND | UserNotFoundException | User not found: usr_123 || ORDER.CONFLICT.ALREADY_CANCELLED | OrderAlreadyCancelledException | Order ord_456 was already cancelled || PAYMENT.DECLINED.INSUFFICIENT_FUNDS | InsufficientFundsException | Payment declined: insufficient funds for $50.00 |Even experienced developers make naming mistakes. Here are the most common anti-patterns and their fixes:
| Mistake | Why It's Bad | Better Alternative |
|---|---|---|
MyAppException | Every exception is "your app's"; no information | OrderValidationException |
Error1, Error2 | Completely meaningless | UserNotFoundException, PaymentDeclinedException |
GenericException | Admits it's generic—so why use it? | Use specific subtypes |
DatabaseException for all DB issues | Lumps together distinct failures | ConnectionTimeoutException, ConstraintViolationException |
BadRequestException | HTTP-centric; domain layer shouldn't know HTTP | ValidationException, MissingFieldException |
check if user exist exception | Not a valid class name format | UserExistenceCheckException or better: UserNotFoundException |
Error code: 42 | Numbers without context are useless | USER.NOT_FOUND |
Error code: userNotFound | Inconsistent casing, no hierarchy | USER.NOT_FOUND |
Message: Error | Says literally nothing | User not found: usr_123 |
Message: Something went wrong | Completely unhelpful for debugging | Payment capture failed: gateway timeout after 30s |
1234567891011121314151617181920212223242526272829303132333435
// ❌ ANTI-PATTERN: Vague generic namespublic class DataException extends Exception { }public class ServiceException extends Exception { }public class ProcessException extends Exception { }// What data? Which service? What process? // ❌ ANTI-PATTERN: Implementation details in namespublic class DatabaseSQLIntegrityConstraintViolationException { }public class HttpClientConnectionPoolTimeoutException { }// Too long; exposes implementation // ❌ ANTI-PATTERN: Action-oriented namespublic class RetryLaterException extends Exception { }public class ContactSupportException extends Exception { }public class FixYourInputException extends Exception { }// Names should describe WHAT happened, not WHAT TO DO // ❌ ANTI-PATTERN: Negative-only namespublic class NotOkException extends Exception { }public class InvalidException extends Exception { }public class FailureException extends Exception { }// What exactly isn't ok/valid/failed? // ❌ ANTI-PATTERN: Redundant namingpublic class ExceptionException extends Exception { } // Seriously?public class ErrorExceptionError extends Exception { } // Worsepublic class UserExceptionUserFailed extends Exception { } // Confused // ✅ GOOD: Clear, specific, domain-oriented namespublic class UserNotFoundException extends EntityNotFoundException { }public class PaymentGatewayTimeoutException extends TechnicalException { }public class InsufficientInventoryException extends OrderException { }public class EmailAlreadyRegisteredException extends ConflictException { }public class PasswordStrengthInsufficientException extends ValidationException { }Would a developer joining your team tomorrow understand what the exception means just from its name? If you need to explain it, consider renaming. Good names are self-documenting; great names tell a story.
Naming is a critical design activity. Well-named exceptions make systems self-documenting, debugging efficient, and operations transparent. Invest time in naming conventions early—they compound across the lifetime of your codebase.
InsufficientFundsException not AddFundsException{Entity}NotFoundException, {Entity}AlreadyExistsException, etc.DOMAIN.CATEGORY.SPECIFIC (e.g., ORDER.INVENTORY.INSUFFICIENT)Module Complete!
You've now learned the complete discipline of exception hierarchy design:
With these skills, you can design exception systems that make error handling clear, debugging efficient, and operations transparent.
You have completed Module 2: Exception Hierarchy Design. You now possess the design knowledge to create world-class exception architectures that serve developers, operators, and end users effectively. Apply these principles consistently across your codebases for robust, maintainable error handling.