> ## Documentation Index
> Fetch the complete documentation index at: https://fastapi2day.codewithsiva.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# FastAPI Exception Handling: HTTP & Validation Errors

> Handle HTTP exceptions, Pydantic validation errors, and unexpected runtime errors in FastAPI using application-level exception handlers.

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:

```text theme={null}
Exception (Python)
│
├── StarletteHTTPException
│       │
│       └── FastAPI HTTPException
│
└── RequestValidationError
```

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

| Exception Type             | Raised By                      | Typical Status Code | Handler                                          |
| -------------------------- | ------------------------------ | ------------------- | ------------------------------------------------ |
| **HTTP Exception**         | Your code or FastAPI/Starlette | 4xx / 5xx           | `@app.exception_handler(StarletteHTTPException)` |
| **RequestValidationError** | Pydantic/FastAPI               | 422                 | `@app.exception_handler(RequestValidationError)` |
| **Programming Exception**  | Python runtime bugs            | 500                 | `@app.exception_handler(Exception)`              |

***

## 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.

```python theme={null}
from fastapi import HTTPException, status

raise HTTPException(
    status_code=status.HTTP_404_NOT_FOUND,
    detail="Employee not found"
)
```

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`:

```python theme={null}
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()

@app.exception_handler(StarletteHTTPException)
def http_exception_handler(request: Request, exception: StarletteHTTPException):
    return JSONResponse(
        status_code=exception.status_code,
        content={
            "error": True,
            "status_code": exception.status_code,
            "message": exception.detail
        }
    )
```

<Note>
  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.
</Note>

***

## 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**.

```python theme={null}
from pydantic import BaseModel

class UserCreate(BaseModel):
    age: int  # must be an integer
```

If the client sends `{"age": "abc"}`, FastAPI raises `RequestValidationError` and returns `422`.

Register a custom handler to format validation errors to your liking:

```python theme={null}
from fastapi.exceptions import RequestValidationError

@app.exception_handler(RequestValidationError)
def validation_exception_handler(request: Request, exception: RequestValidationError):
    errors = exception.errors()
    return JSONResponse(
        status_code=422,
        content={
            "error": True,
            "message": "Validation failed",
            "details": [
                {
                    "field": ".".join(str(loc) for loc in err["loc"]),
                    "issue": err["msg"]
                }
                for err in errors
            ]
        }
    )
```

***

## 3. Programming Exceptions

These are **unexpected runtime errors** caused by bugs in your code — division by zero, attribute access on `None`, key errors, etc.:

```python theme={null}
result = 10 / 0          # ZeroDivisionError
user.name                # AttributeError if user is None
data["missing_key"]      # KeyError
```

Register a global fallback handler to catch anything not caught by the more specific handlers above:

```python theme={null}
@app.exception_handler(Exception)
def global_exception_handler(request: Request, exception: Exception):
    return JSONResponse(
        status_code=500,
        content={
            "error": True,
            "message": "An unexpected internal server error occurred.",
            "type": type(exception).__name__
        }
    )
```

<Warning>
  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.
</Warning>

***

## Request Flow with Exception Handling

```text theme={null}
                    Client Request
                          │
                          ▼
                 Request Validation
                          │
             ┌────────────┴────────────┐
             │                         │
             ▼                         ▼
  Validation Failed              Route Function
             │                         │
             ▼                         ▼
  RequestValidationError      Exception Raised?
                                       │
                          ┌────────────┴────────────┐
                          │                         │
                          ▼                         ▼
                   HTTPException        Programming Exception
                          │                         │
                          ▼                         ▼
           StarletteHTTPException       Exception Handler
                    Handler                  (500)
```

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:

```text theme={null}
app/
│
├── main.py
├── core/
│   └── exceptions.py     ← all handlers defined here
└── routers/
    ├── employees.py
    └── auth.py
```

Define a registration function in `core/exceptions.py`:

```python theme={null}
# core/exceptions.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException

def register_exception_handlers(app: FastAPI) -> None:

    @app.exception_handler(StarletteHTTPException)
    async def http_handler(request: Request, exc: StarletteHTTPException):
        return JSONResponse(
            status_code=exc.status_code,
            content={"error": True, "message": exc.detail}
        )

    @app.exception_handler(RequestValidationError)
    async def validation_handler(request: Request, exc: RequestValidationError):
        return JSONResponse(
            status_code=422,
            content={"error": True, "message": "Validation failed", "details": exc.errors()}
        )

    @app.exception_handler(Exception)
    async def global_handler(request: Request, exc: Exception):
        return JSONResponse(
            status_code=500,
            content={"error": True, "message": "Internal server error"}
        )
```

Register them once in `main.py`:

```python theme={null}
# main.py
from fastapi import FastAPI
from core.exceptions import register_exception_handlers
from routers import employees, departments

app = FastAPI()

register_exception_handlers(app)  # register all handlers first

app.include_router(employees.router)
app.include_router(departments.router)
```

Every router automatically uses these handlers:

```text theme={null}
            FastAPI Application
                    │
    register_exception_handlers()
                    │
    ┌───────────────┼───────────────┐
    │               │               │
    ▼               ▼               ▼
Employees       Departments      Auth Router
  Router          Router
```

***

## Rule of Thumb

| When to use                                                 | What to do                                         |
| ----------------------------------------------------------- | -------------------------------------------------- |
| Intentional error (resource not found, conflict, bad input) | `raise HTTPException(status_code=..., detail=...)` |
| Customise HTTP error format                                 | Handle `StarletteHTTPException`                    |
| Customise validation error format                           | Handle `RequestValidationError`                    |
| Catch unexpected bugs                                       | Handle `Exception` as a global fallback            |
