Skip to main content
When building production APIs, handling errors gracefully is just as important as the happy path. FastAPI gives you three distinct types of exceptions to contend with — expected HTTP errors you raise intentionally, validation errors that Pydantic fires before your code even runs, and unexpected programming bugs that slip through. This lesson explains each type, shows you how to register custom handlers, and describes the right project structure for keeping exception logic clean and centralised.

Exception Hierarchy

Understanding how these exception types relate to each other is the first step to handling them correctly:
Key points:
  • FastAPI HTTPException is a subclass of StarletteHTTPException.
  • RequestValidationError is not an HTTPException — it’s a separate class.
  • All of them ultimately inherit from Python’s Exception.

The Three Exception Types


1. HTTP Exceptions

HTTP exceptions represent expected errors that you intentionally raise when a business condition fails — a resource isn’t found, a user lacks permission, or a conflict exists.
Register a custom handler using StarletteHTTPException (not FastAPI HTTPException) so that it also catches errors raised internally by FastAPI and Starlette, such as 404 Route Not Found and 405 Method Not Allowed:
Using StarletteHTTPException in your handler catches both exceptions raised by your code (raise HTTPException(404, ...)) and those raised internally by FastAPI — such as when a client requests a route that doesn’t exist.

2. Request Validation Exceptions

Before FastAPI calls your route function, Pydantic validates the incoming data. If validation fails — wrong type, missing required field, value out of range — FastAPI raises a RequestValidationError. Your function is never executed.
If the client sends {"age": "abc"}, FastAPI raises RequestValidationError and returns 422. Register a custom handler to format validation errors to your liking:

3. Programming Exceptions

These are unexpected runtime errors caused by bugs in your code — division by zero, attribute access on None, key errors, etc.:
Register a global fallback handler to catch anything not caught by the more specific handlers above:
In production, never expose raw exception messages or stack traces in the response body — they can leak implementation details to attackers. Log the full error server-side and return only a generic message to the client.

Request Flow with Exception Handling

FastAPI always chooses the most specific matching handler. A HTTPException will never fall through to the generic Exception handler.

Project Structure for Exception Handlers

Exception handlers are registered once for the entire application — not inside individual routers. A clean pattern is to centralise them in a single file:
Define a registration function in core/exceptions.py:
Register them once in main.py:
Every router automatically uses these handlers:

Rule of Thumb