Exception Handling (HTTPException)
Raise and handle HTTP exceptions in FastAPI with status codes, detail messages, and custom exception handlers
Raising HTTPException
FastAPI provides HTTPException to return HTTP error responses directly from path operations:
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {1: "Laptop", 2: "Mouse", 3: "Keyboard"}
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item_id": item_id, "name": items[item_id]}
When HTTPException is raised, the handler function stops immediately and FastAPI sends the JSON response.
Status Codes & Detail Messages
The status_code parameter accepts any integer, but you should use Starlette’s status constants for readability:
from starlette import status
@app.get("/products/{product_id}")
async def read_product(product_id: int):
if product_id <= 0:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Product ID must be positive",
)
if product_id > 1000:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Product {product_id} does not exist",
)
return {"product_id": product_id}
The detail field can be a string, dict, or list:
@app.post("/validate")
async def validate(data: dict):
errors = []
if "name" not in data:
errors.append({"field": "name", "message": "Name is required"})
if "email" not in data:
errors.append({"field": "email", "message": "Email is required"})
if errors:
raise HTTPException(status_code=422, detail=errors)
return {"valid": True}
Headers in HTTPException
You can attach custom headers to the response, useful for rate-limiting or redirects:
@app.get("/rate-limited")
async def rate_limited():
raise HTTPException(
status_code=429,
detail="Too many requests",
headers={"Retry-After": "120", "X-RateLimit-Reset": "1700000000"},
)
Custom Exception Handlers with @app.exception_handler
Register a handler for a specific exception class using the @app.exception_handler decorator:
from starlette.requests import Request
from starlette.responses import JSONResponse
class InsufficientStockError(Exception):
def __init__(self, item_id: int, available: int, requested: int):
self.item_id = item_id
self.available = available
self.requested = requested
@app.exception_handler(InsufficientStockError)
async def insufficient_stock_handler(request: Request, exc: InsufficientStockError):
return JSONResponse(
status_code=400,
content={
"error": "INSUFFICIENT_STOCK",
"message": f"Item {exc.item_id} has only {exc.available} in stock",
"available": exc.available,
"requested": exc.requested,
},
)
@app.post("/order")
async def place_order(item_id: int, quantity: int):
stock = {1: 10, 2: 5}
available = stock.get(item_id, 0)
if quantity > available:
raise InsufficientStockError(item_id, available, quantity)
return {"success": True}
Handling RequestValidationError
FastAPI uses RequestValidationError internally when request data fails Pydantic validation. Override it to customise the 422 response:
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=422,
content={
"error": "VALIDATION_ERROR",
"detail": exc.errors(),
"body": exc.body,
},
)
Overriding Default Exception Handlers
You can override FastAPI’s default handlers for HTTPException and RequestValidationError globally:
@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
return JSONResponse(
status_code=exc.status_code,
content={
"error": "HTTP_ERROR",
"detail": exc.detail,
"status_code": exc.status_code,
},
)
Exception Handler Registration Order
Handlers registered later override earlier ones for the same exception. More specific exceptions take priority over general ones:
# General handler - catches everything
@app.exception_handler(Exception)
async def generic_handler(request: Request, exc: Exception):
return JSONResponse(status_code=500, content={"error": "Internal server error"})
# Specific handler - takes precedence for ValueError
@app.exception_handler(ValueError)
async def value_error_handler(request: Request, exc: ValueError):
return JSONResponse(status_code=400, content={"error": str(exc)})
Key Takeaways
HTTPExceptionstops execution and returns a JSON error response immediately.- Use
status_codeconstants fromstarlette.statusfor clarity. - The
detailfield accepts strings, dicts, or lists for structured errors. - Attach custom headers for rate-limiting info, redirects, or metadata.
@app.exception_handlerregisters custom handlers for any exception type.- Override
RequestValidationErrorto customise the default 422 validation response. - More specific exception handlers take precedence over general ones.
Keep learning
Related tutorials from across the library.
Custom Error Responses
Design a unified error response system with custom exception classes, error schemas, and global handlers
Web Development / Backend / FastAPI / Error Handling & Testing
Unit Testing with Pytest
Write and run async unit tests with pytest, pytest-asyncio, fixtures, parametrization, and mocking in FastAPI
Web Development / Backend / FastAPI / Error Handling & Testing
API Testing with TestClient
Test FastAPI endpoints end-to-end using TestClient, including auth headers, file uploads, and JSON assertions
Web Development / Backend / FastAPI / Error Handling & Testing