When should you create your own exceptions?
Create your own exception when:
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/elseorOptional - Build hierarchy:
BusinessException->OrderException->OrderNotFoundExceptionfor 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” -
IllegalArgumentExceptionis 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)]]