Short answer
Constructing an exception captures a full stack trace (fillInStackTrace), which is the expensive part, so throwing exceptions on a hot path for normal outcomes is a real performance hit and obscures actual errors.
Explanation
Using exceptions for control flow (e.g., catching to break a loop) is both slow and unreadable; return an Optional, a result type, or a sentinel for expected 'not found' cases. If you must signal a frequent expected condition via exception, you can override fillInStackTrace to skip the trace.
Common mistakes
- Using exceptions for normal flow such as loop exit or validation.
- Ignoring the cost of filling stack traces in hot paths.