08/22/2026
An entity isn't found. That's not an exceptional situation.
But many devs still write `throw new TodoNotFoundException(id)`, and then we pay for it:
- The failure is invisible. Nothing in the method signature tells you it can happen.
- It is easy to forget the `catch`, and you find out in production.
- Throwing is slow when it happens a lot.
But my biggest gripe with this approach is the added indirection.
Using exceptions for flow control is a bad practice, and we should stop doing it.
The other option is to return the failure instead of throwing it.
`return Result.Failure(TodoItemErrors.NotFound(id));`
Now the failure is part of the type. The compiler makes the caller deal with it. And you can read a handler and see every way it can go wrong, without hunting for `throw` statements.
Exceptions still have a job. They are for real bugs, the things you did not plan for. My
template keeps that line clear: handlers return `Result`, and one global handler turns errors into a proper `ProblemDetails` response.
Every handler in my free Clean Architecture template already works this way:
Follow me for more videos and codes: Code Adventure