IMPLEMENTATION QUALITY / OBJECT DESIGN BRIEF

Domain exceptions

Domain exceptions are error types named after business problems, such as InsufficientFundsException, SeatAlreadyBookedException, or LotFullException, rather than generic RuntimeException or Error('failed').

IntermediatePhase 07 / Topic 1 of 8ResponsibilitiesCollaborationsExtensibility
01

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.

Specific error messages at a bank

'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.

02

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.
03

Where it shows up in interviews

Error modeling

Recognize it when: 'what happens if...' questions.

  • Design an ATM
  • Design a ticket booking system
  • Design a parking lot
04

Where it is used in real software

Stripe error types

CardError, RateLimitError, and InvalidRequestError let clients handle each case.

Spring @ControllerAdvice

Maps domain exceptions to HTTP responses in one place.

RFC 9457 Problem Details

Standard JSON format for API errors with type, title, and detail.

05

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.
06

How it works, step by step

  1. 1
    List business failures from use cases

    Seat taken, payment declined, hold expired.

  2. 2
    Create a small hierarchy

    BookingException > SeatUnavailable, HoldExpired.

  3. 3
    Include context

    IDs and values, not sensitive data.

  4. 4
    Translate at boundaries

    SQL errors to domain errors; domain errors to HTTP.

  5. 5
    Test failures explicitly

    assertThrows(SeatUnavailable.class, ...).

07

Mapping errors at the API boundary

Booking service errors

Step 1 / 5
ExceptionKindHTTP statusUser message
SeatUnavailableExceptionBusiness409 ConflictThat seat was just taken
HoldExpiredExceptionBusiness410 GoneYour hold expired, please reselect
PaymentDeclinedExceptionBusiness402 Payment RequiredCard declined
IllegalArgumentExceptionProgrammer / validation400 Bad RequestInvalid request
DataAccessExceptionInfrastructure503 Service UnavailableTry 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.

08

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);    }}
09

Complexity and performance

Exception typesA few per bounded context

Not one per method.

Throw costStack trace capture

Avoid for hot-path control flow.

10

Trade-offs

Exceptions vs result types

Exceptions keep happy paths clean but hide failure paths in signatures; result types are explicit but more verbose.

Granularity

Too many types become noise; group by how callers react.

11

Variants and related techniques

Error codes in a sealed hierarchy

Exhaustive handling with pattern matching.

Notification pattern

Collect multiple validation errors instead of failing on the first.

12

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.

13

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.

14

Practice problems

ProblemDifficultyWhat it trains
Design exceptions for a library checkoutEasyBusiness failures.
Map domain errors to HTTP in a booking APIMediumBoundary translation.