Exception designmedium0-2 years

A service throws a single `ServiceException` for everything — not found, validation failure, and a downstream timeout all end up as the same type with different messages. What's wrong with that design, and how would you restructure the hierarchy?

A single catch-all exception type throws away the one piece of information a boundary handler actually needs: whether the request was understood and refused (the caller's fault — not found, a conflict, an invalid state) or the system genuinely failed (nobody's fault at the API level — a downstream timeout, a database outage). Those two categories map to completely different HTTP responses — 4xx versus 5xx — and need completely different handling: a not-found is a normal, expected outcome worth logging at most at INFO, while a downstream timeout is a real operational problem that needs the full stack trace at ERROR. With one flat ServiceException, the boundary handler has to parse the message string to guess which is which, which is fragile and eventually wrong. The fix is a small, deliberate hierarchy: an abstract BusinessException (with a stable, machine-readable code) for caller-fault outcomes, and a separate InfrastructureException for system failures — few types, not one per error, with the specific case carried by the code field rather than by the class.

The lesson behind it →
More on Exception design