Question 14 · Section 7

When should you create your own exceptions?

Create your own exception when:

Language versions: English Russian Ukrainian

Junior Level

When you SHOULD create

1. For business errors:

public class InsufficientFundsException extends RuntimeException {
    public InsufficientFundsException(String message) {
        super(message);
    }
}

2. When you need to pass additional information:

public class OrderTimeoutException extends RuntimeException {
    private final Long orderId;
    private final int timeoutSeconds;

    public OrderTimeoutException(Long orderId, int timeoutSeconds) {
        super("Order " + orderId + " timed out after " + timeoutSeconds + "s");
        this.orderId = orderId;
        this.timeoutSeconds = timeoutSeconds;
    }
}

3. When standard exceptions don’t fit:

// Instead of generic IllegalArgumentException
throw new InvalidEmailException("Email must contain @ and domain");

When you should NOT create

Don’t create if standard ones fit:

// No need for custom exception
if (age < 0) throw new IllegalArgumentException("Age < 0");

// No need for custom exception
if (user == null) throw new NullPointerException("User is null");

Simple rule

Create your own exception when:

  • It’s a business error (not a code bug)
  • You need additional fields
  • You need a separate HTTP status

Custom or standard exception?

Create custom if you need:

  • A separate catch block for this error type
  • A separate HTTP status (404, 409, 422)
  • Additional fields (orderId, retryAfter)

Use standard if:

  • Logging a message is sufficient
  • Error is generic and doesn’t need special handling

Middle Level

Architectural cost of custom exceptions

Each new exception is a class in Metaspace. In large systems with thousands of exceptions, this increases startup time and memory consumption.

When to create - in detail

1. Business validation:

throw new CouponExpiredException("Coupon expired on " + expiryDate);
// @ControllerAdvice will turn into 400 Bad Request with JSON

2. Exception Translation at layer boundary:

try {
    repository.save(order);
} catch (SQLException e) {
    throw new DataAccessException("Failed to save order", e);
}

3. Informativeness: Need additional fields: retryAfter, errorCode, entityId.

When NOT to create

1. Flow Control - anti-pattern:

// ANTI-PATTERN: exceptions as if-else
try {
    User user = findUser(id); // throws UserFoundException if found
    // next step...
} catch (UserFoundException e) {
    // "Normal" flow through exception!
}

// Instead:
Optional<User> user = findUser(id);
if (user.isPresent()) { /* next step */ }

Benchmarks show 100-1000x slowdown. Absolute values: returning Optional ~10ns, creating exception ~1-5 microseconds (depends on stack depth and JVM).

2. JDK duplication:

// No need if IllegalArgumentException describes the situation
throw new InvalidAgeException("Age < 0");
// Better:
throw new IllegalArgumentException("Age must be positive, got: " + age);

Exception hierarchy

RuntimeException
  -- BusinessException
        -- OrderException
        |     -- OrderNotFoundException
        |     -- OrderAlreadyShippedException
        -- PaymentException
              -- InsufficientFundsException
              -- PaymentProviderException

Senior Level

Domain-Driven Exceptions

In quality architecture, an exception is a domain model event, not just an “error”.

Immutable Exceptions for Highload

For frequently occurring business errors that don’t require stack debugging:

public class FastValidationException extends RuntimeException {
    public FastValidationException(String message) {
        super(message, null, false, false);
    }

    // Predefined instances
    public static final FastValidationException EMPTY_EMAIL =
        new FastValidationException("Email is required");
    public static final FastValidationException INVALID_PHONE =
        new FastValidationException("Invalid phone format");
}

Exception Hierarchy and grouping

Build hierarchy for group catching:

// Can catch all business errors
catch (BusinessException e) { return 400; }

// Or more specific
catch (OrderNotFoundException e) { return 404; }

Serialization and microservices

public class BusinessException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    // Without this - InvalidClassException on deserialization
}

PII Leak and security

Don’t add fields like creditCardNumber to exceptions. If it gets into logs - PCI DSS violation.

PII (Personally Identifiable Information) - personal data. PCI DSS - payment card security standard. Card data in logs = standard violation.

Metric-driven analysis

// Count via Micrometer
meterRegistry.counter("exceptions.coupon_expired").increment();

// Sharp rise in CouponExpiredException - business indicator

Error Codes

Each custom exception - unique string code:

public enum ErrorCode {
    ERR_USER_NOT_FOUND("USR-001"),
    ERR_INSUFFICIENT_FUNDS("PAY-002"),
    ERR_COUPON_EXPIRED("COU-003");

    private final String code;
}

Frontend uses code for localization.


Interview Cheat Sheet

Must know:

  • Create custom exception for business errors when you need additional fields or separate HTTP status
  • DON’T use exceptions as flow control (anti-pattern) - 100-1000x slower than if/else or Optional
  • Build hierarchy: BusinessException -> OrderException -> OrderNotFoundException for grouping catch blocks
  • Each custom exception - unique string code (ERR-001) for localization and frontend
  • Don’t expose PII in messages (passwords, card numbers) - PCI DSS violation
  • For highload: immutable exceptions with predefined instances and writableStackTrace = false

Frequent follow-up questions:

  • How to decide: custom or standard? - If you need a separate catch block, HTTP status, or additional fields - custom; if message is enough - standard
  • Why are exceptions as flow control an anti-pattern? - Stack trace creation costs 1-5 microseconds, Optional - ~10ns; plus code becomes unreadable
  • How to organize exception hierarchy? - Common parent (BusinessException) for group catching + specific subclasses for individual HTTP statuses
  • What to do with exceptions in microservices? - At the boundary, translate to standard format (RFC 7807), inside service use custom

Red flags (NOT to say):

  • “I use exceptions instead of if-else” - This is an anti-pattern with serious performance degradation
  • “I create an exception for each validation” - IllegalArgumentException is usually enough
  • “I don’t care about message security” - PII in logs = GDPR/PCI DSS violation

Related topics:

  • [[13. Can you create custom exceptions]]
  • [[15. What is better extend Exception or RuntimeException]]
  • [[15. What is a stack trace]]
  • [[17. How to properly log exceptions]]
  • [[19. Why you should not swallow exceptions (catch empty)]]