Interview Prep Zoneby CuriouserLabs

Question 5 of 5 · staff level

How do you design a custom exception hierarchy and error model for a large codebase?

🎤 Say this first

Design exceptions like an API: a shallow hierarchy rooted in one or two base types (DomainException, InfrastructureException), each exception carrying structured context (error code, offending IDs, retryability) — not just a message string. Error codes form a stable, documented contract for clients and support; messages stay human and log-friendly; and construction is cheap unless you need the stack (consider writableStackTrace=false for high-frequency control-flow-adjacent cases — sparingly).

The full picture

An exception that carries its context
public class DomainException extends RuntimeException {
    private final ErrorCode code;          // enum: stable, documented
    private final Map<String, Object> context;
    private final boolean retryable;

    protected DomainException(ErrorCode code, String msg, Throwable cause,
                              boolean retryable, Map<String, Object> ctx) {
        super(msg, cause);
        this.code = code; this.retryable = retryable;
        this.context = Map.copyOf(ctx);
    }
}

public final class InsufficientStockException extends DomainException {
    public InsufficientStockException(SkuId sku, int requested, int available) {
        super(ErrorCode.STOCK_INSUFFICIENT,
              "Requested %d of %s, only %d available".formatted(requested, sku, available),
              null, false,
              Map.of("sku", sku.value(), "requested", requested, "available", available));
    }
}
  • Shallow beats deep: 2 levels is usually enough; deep trees invite catch targeting fragile intermediate types.
  • Codes vs types: types drive program behavior (catch/retry/map-to-status); codes drive human and client behavior (docs, support, i18n). You want both, generated into API docs.
  • Context discipline: IDs yes, PII/secrets never — exceptions end up in logs, APMs, and sometimes client responses.
  • Don't: one giant AppException with an int code for everything (stringly-typed control flow), or exceptions for expected branches (parse failures in a validator loop — return results).

🔄 Likely follow-up questions

  • How do error codes map to RFC 9457 problem types across services?
  • How do you keep exception context out of client responses but in logs?
  • Exceptions vs sealed result types for expected failures — where's your line?