Когда и зачем создавать пользовательские исключения (Custom Exception) в Java?

«Когда и зачем создавать пользовательские исключения (Custom Exception) в Java?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Пользовательские исключения создают, когда стандартных исключений Java (IllegalArgumentException, RuntimeException и др.) недостаточно для точной и понятной передачи семантики конкретной бизнес- или логической ошибки приложения.

Основные причины для создания:

  • Четкая классификация ошибок: Отделить ошибки вашего домена от системных.
  • Дополнительная информация: Возможность добавить в исключение специфичные поля (ID операции, код ошибки, значение параметра).
  • Улучшение читаемости кода: Имя исключения (InsufficientFundsException) понятнее, чем общее IllegalStateException.
  • Тонкое управление обработкой: Позволяет точнее ловить (catch) определенные типы сбоев.

Практический пример:

// 1. Создание непроверяемого (unchecked) пользовательского исключения
public class InsufficientFundsException extends RuntimeException {
    private final String accountId;
    private final BigDecimal requiredAmount;

    public InsufficientFundsException(String accountId, BigDecimal requiredAmount, BigDecimal currentBalance) {
        super(String.format("Account %s has insufficient funds. Required: %s, Available: %s",
                accountId, requiredAmount, currentBalance));
        this.accountId = accountId;
        this.requiredAmount = requiredAmount;
    }
    // Геттеры для дополнительных полей
}

// 2. Использование в сервисе
public void withdraw(String accountId, BigDecimal amount) {
    BigDecimal balance = getBalance(accountId);
    if (balance.compareTo(amount) < 0) {
        throw new InsufficientFundsException(accountId, amount, balance);
    }
    // ... логика списания
}

// 3. Точечная обработка на верхнем уровне (например, в контроллере)
@ExceptionHandler(InsufficientFundsException.class)
public ResponseEntity<ErrorResponse> handleInsufficientFunds(InsufficientFundsException ex) {
    // Можно использовать ex.getAccountId() для формирования детального ответа клиенту
    return ResponseEntity.status(HttpStatus.CONFLICT).body(new ErrorResponse(ex.getMessage()));
}

Важно: Не злоупотребляйте созданием исключений для тривиальных случаев, где достаточно стандартных. Наследуйтесь от RuntimeException для непроверяемых исключений (чаще всего) или от Exception для проверяемых (если обработка ошибки обязательна для вызывающего кода).