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.