Overview
Domain exceptions are error types named after business problems, such as InsufficientFundsException, SeatAlreadyBookedException, or LotFullException, rather than generic RuntimeException or Error('failed'). They make failures part of the model: callers can catch specific cases, map them to the right HTTP status or UI message, and tests can assert exact failures.
A good error strategy separates expected business outcomes (seat taken, card declined) from programmer errors (null argument, illegal state) and infrastructure failures (database down). Some teams use result types for expected outcomes and exceptions for the rest; either way, errors carry useful context and never leak internals to users.
'Transaction failed' is frustrating. 'Insufficient funds: balance 40, requested 100' tells you exactly what happened and what to do. Domain exceptions are the specific messages of your code.
When to use it
- Business rules can reject an operation.
- Callers need to react differently to different failures.
- APIs must map failures to status codes or messages.
Where it shows up in interviews
Recognize it when: 'what happens if...' questions.
- Design an ATM
- Design a ticket booking system
- Design a parking lot
Where it is used in real software
CardError, RateLimitError, and InvalidRequestError let clients handle each case.
Maps domain exceptions to HTTP responses in one place.
Standard JSON format for API errors with type, title, and detail.
Key terms
- Domain exception
- Error type representing a business rule violation.
- Checked vs unchecked
- Java: compiler-enforced handling vs runtime.
- Result type
- Return success or failure as a value (Either, Result).
- Error translation
- Map low-level errors to domain or API errors.
- Fail fast
- Detect and report problems immediately.
How it works, step by step
- 1List business failures from use cases
Seat taken, payment declined, hold expired.
- 2Create a small hierarchy
BookingException > SeatUnavailable, HoldExpired.
- 3Include context
IDs and values, not sensitive data.
- 4Translate at boundaries
SQL errors to domain errors; domain errors to HTTP.
- 5Test failures explicitly
assertThrows(SeatUnavailable.class, ...).
Mapping errors at the API boundary
Booking service errors
| Exception | Kind | HTTP status | User message |
|---|---|---|---|
| SeatUnavailableException | Business | 409 Conflict | That seat was just taken |
| HoldExpiredException | Business | 410 Gone | Your hold expired, please reselect |
| PaymentDeclinedException | Business | 402 Payment Required | Card declined |
| IllegalArgumentException | Programmer / validation | 400 Bad Request | Invalid request |
| DataAccessException | Infrastructure | 503 Service Unavailable | Try again shortly |
NOWException: SeatUnavailableException | Kind: Business | HTTP status: 409 Conflict | User message: That seat was just taken
Specific types enable precise handling; internals such as SQL messages never reach users.
Implementation
public abstract class BookingException extends RuntimeException { protected BookingException(String message) { super(message); }} public final class SeatUnavailableException extends BookingException { private final String seatId; public SeatUnavailableException(String seatId) { super("Seat " + seatId + " is not available"); this.seatId = seatId; } public String seatId() { return seatId; }} public final class HoldExpiredException extends BookingException { public HoldExpiredException(String holdId) { super("Hold " + holdId + " expired"); }} @RestControllerAdviceclass BookingErrors { @ExceptionHandler(SeatUnavailableException.class) ResponseEntity<ProblemDetail> seat(SeatUnavailableException e) { var p = ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, e.getMessage()); p.setProperty("seatId", e.seatId()); return ResponseEntity.status(HttpStatus.CONFLICT).body(p); }}Complexity and performance
Not one per method.
Avoid for hot-path control flow.
Trade-offs
Exceptions keep happy paths clean but hide failure paths in signatures; result types are explicit but more verbose.
Too many types become noise; group by how callers react.
Variants and related techniques
Exhaustive handling with pattern matching.
Collect multiple validation errors instead of failing on the first.
Common mistakes
- Catching Exception and ignoring it.
Fix: Catch specific types, log, and rethrow or handle meaningfully.
- Leaking stack traces or SQL errors to users.
Fix: Translate at the boundary; log details internally.
- Exceptions for normal control flow in hot loops.
Fix: Use return values for expected frequent outcomes.
Interview questions
How do you handle errors in an LLD solution?
Define domain exceptions for business rule violations with context, validate inputs early, translate infrastructure errors at boundaries, and map domain errors to user-facing responses in one place.
Checked or unchecked exceptions in Java?
Most modern Java code uses unchecked domain exceptions to avoid signature pollution, documenting them and handling them at boundaries; checked exceptions suit recoverable conditions callers must handle immediately.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Design exceptions for a library checkout | Easy | Business failures. |
| Map domain errors to HTTP in a booking API | Medium | Boundary translation. |