Коли варто створювати свої винятки?
Створюйте свій виняток, коли:
Junior Level
Коли СЛІД створювати
1. Для бізнес-помилок:
public class InsufficientFundsException extends RuntimeException {
public InsufficientFundsException(String message) {
super(message);
}
}
2. Коли потрібно передати додаткову інформацію:
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. Коли стандартні винятки не підходять:
// Замість загального IllegalArgumentException
throw new InvalidEmailException("Email must contain @ and domain");
Коли НЕ СЛІД створювати
Не створюйте, якщо підходять стандартні:
// Не потрібен свій виняток
if (age < 0) throw new IllegalArgumentException("Age < 0");
// Не потрібен свій виняток
if (user == null) throw new NullPointerException("User is null");
Просте правило
Створюйте свій виняток, коли:
- Це бізнес-помилка (не баг коду)
- Потрібні додаткові поля
- Потрібен окремий HTTP-статус
Кастомний чи стандартний виняток?
Створюйте кастомний, якщо потрібно:
- Окремий catch-блок для цього типу помилки
- Окремий HTTP-статус (404, 409, 422)
- Додаткові поля (orderId, retryAfter)
Використовуйте стандартний, якщо:
- Достатньо логувати повідомлення
- Помилка однотипна і не потребує спеціальної обробки
Middle Level
Архітектурна ціна кастомних винятків
Кожен новий виняток — це клас у Metaspace. У великих системах з тисячами винятків це збільшує час старту та споживання пам’яті.
Коли створювати — детальніше
1. Бізнес-валідація:
throw new CouponExpiredException("Coupon expired on " + expiryDate);
// @ControllerAdvice перетворить на 400 Bad Request з JSON
2. Exception Translation на межі шарів:
try {
repository.save(order);
} catch (SQLException e) {
throw new DataAccessException("Failed to save order", e);
}
3. Інформативність:
Потрібні дод. поля: retryAfter, errorCode, entityId.
Коли НЕ створювати
1. Flow Control — антипатерн:
// ❌ АНТИПАТЕРН: винятки як if-else
try {
User user = findUser(id); // кидає UserFoundException якщо знайдений
// наступний крок...
} catch (UserFoundException e) {
// «Нормальний» хід виконання через виняток!
}
// ✅ Замість цього:
Optional<User> user = findUser(id);
if (user.isPresent()) { /* наступний крок */ }
Бенчмарки показують 100-1000x уповільнення. На абсолютних значеннях: повернення Optional ~10нс, створення винятку ~1-5 мкс (залежить від глибини стеку та JVM).
2. Дублювання JDK:
// Не потрібно, якщо IllegalArgumentException описує ситуацію
throw new InvalidAgeException("Age < 0");
// Краще:
throw new IllegalArgumentException("Age must be positive, got: " + age);
Ієрархія винятків
RuntimeException
└── BusinessException
├── OrderException
│ ├── OrderNotFoundException
│ └── OrderAlreadyShippedException
└── PaymentException
├── InsufficientFundsException
└── PaymentProviderException
Senior Level
Domain-Driven Exceptions
У якісній архітектурі виняток — це подія доменної моделі, а не просто “помилка”.
Immutable Exceptions для Highload
Для часто виникаючих бізнес-помилок, які не потребують дебагу по стеку:
public class FastValidationException extends RuntimeException {
public FastValidationException(String message) {
super(message, null, false, false);
}
// Попередньо визначені екземпляри
public static final FastValidationException EMPTY_EMAIL =
new FastValidationException("Email is required");
public static final FastValidationException INVALID_PHONE =
new FastValidationException("Invalid phone format");
}
Exception Hierarchy і групування
Будуйте ієрархію для перехоплення групами:
// Можна ловити всі бізнес-помилки
catch (BusinessException e) { return 400; }
// Або конкретніше
catch (OrderNotFoundException e) { return 404; }
Serialization та мікросервіси
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
// Без цього — InvalidClassException при десеріалізації
}
PII Leak та безпека
Не додавайте поля типу creditCardNumber у винятки. Якщо потрапить у логи — порушення PCI DSS.
PII (Personally Identifiable Information) – персональні дані. PCI DSS – стандарт безпеки платіжних карток. Потрапляння даних карток у логи = порушення стандарту.
Metric-driven analysis
// Рахуйте через Micrometer
meterRegistry.counter("exceptions.coupon_expired").increment();
// Різкий ріст CouponExpiredException — бізнес-індикатор
Error Codes
Кожен кастомний виняток — унікальний рядковий код:
public enum ErrorCode {
ERR_USER_NOT_FOUND("USR-001"),
ERR_INSUFFICIENT_FUNDS("PAY-002"),
ERR_COUPON_EXPIRED("COU-003");
private final String code;
}
Фронтенд використовує код для локалізації.
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- Створюйте кастомний виняток для бізнес-помилок, коли потрібні додаткові поля або окремий HTTP-статус
- НЕ використовуйте винятки як flow control (антипатерн) — це у 100-1000x повільніше
if/elseабоOptional - Будуйте ієрархію:
BusinessException→OrderException→OrderNotFoundExceptionдля групування catch-блоків - Кожен кастомний виняток — унікальний рядковий код (
ERR-001) для локалізації та фронтенду - Не розкривайте PII у повідомленнях (паролі, номери карток) — порушення PCI DSS
- Для highload: immutable exceptions з попередньо визначеними екземплярами та
writableStackTrace = false
Часті уточнюючі запитання:
- Як вирішити: кастомний чи стандартний виняток? — Якщо потрібен окремий catch-блок, HTTP-статус або дод. поля — кастомний; якщо достатньо повідомлення — стандартний
- Чому винятки як flow control — антипатерн? — Створення stack trace коштує 1-5 мкс, а
Optional— ~10 нс; плюс код стає нечитабельним - Як організувати ієрархію винятків? — Загальний батько (
BusinessException) для групового перехоплення + конкретні підкласи для окремих HTTP-статусів - Що робити з винятками у мікросервісах? — На межі перекладати у стандартний формат (RFC 7807), всередині сервісу використовувати кастомні
Червоні прапорці (НЕ говорити):
- “Використовую винятки замість if-else” — Це антипатерн з серйозним падінням продуктивності
- “Створюю виняток для кожної валідації” —
IllegalArgumentExceptionзазвичай достатньо - “Не дбаю про безпеку повідомлень” — PII в логах = порушення GDPR/PCI DSS
Пов’язані теми:
- [[Чи можна створювати власні винятки]]
- [[Що краще: наслідувати Exception чи RuntimeException]]
- [[Що таке stack trace]]
- [[Як правильно логувати винятки]]
- [[Чому не варто ковтати винятки (catch empty)]]