How does HTTPException work in FastAPI?
HTTPException is FastAPI's built-in way to return HTTP error responses from endpoints and dependencies. It immediately stops request processing and sends a JSON error response.
How it works:
- You
raise HTTPException(status_code=..., detail=...)anywhere in your endpoint or dependency - FastAPI catches it and returns a JSON response with the given status code
- The
detailfield can be a string, dict, or list -- it is JSON-serialized automatically - An optional
headersdict adds extra HTTP headers to the error response
Parameters:
status_code(int): The HTTP status code (400, 401, 403, 404, 422, 500, etc.)detail(Any): The error message or structured error dataheaders(dict | None): Additional response headers (e.g.,WWW-Authenticatefor 401)
Default response format:
{"detail": "your error message"}
Common status codes:
| Code | Meaning | Use case |
|---|---|---|
| 400 | Bad Request | Invalid input that passes validation |
| 401 | Unauthorized | Missing or invalid auth |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource does not exist |
| 409 | Conflict | Duplicate resource |
| 422 | Unprocessable Entity | Validation error (FastAPI's default) |
# Simple string error
raise HTTPException(status_code=404, detail="Item not found")
# Structured error with dict
raise HTTPException(status_code=400, detail={"field": "email", "error": "invalid"})
# With custom headers
raise HTTPException(
status_code=401,
detail="Token expired",
headers={"WWW-Authenticate": "Bearer"},
)
from fastapi import FastAPI, HTTPException, status
from fastapi.testclient import TestClient
app = FastAPI()
# Simulated database
ITEMS = {1: {"name": "Widget", "price": 9.99}, 2: {"name": "Gadget", "price": 24.99}}
@app.get("/items/{item_id}")
def get_item(item_id: int):
if item_id < 0:
# String detail
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Item ID must be positive",
)
if item_id not in ITEMS:
# Structured detail (dict)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail={"error": "not_found", "item_id": item_id},
)
return ITEMS[item_id]
@app.delete("/items/{item_id}")
def delete_item(item_id: int):
if item_id not in ITEMS:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Item not found",
headers={"X-Error-Code": "ITEM_NOT_FOUND"},
)
return {"deleted": item_id}
# --- Test ---
client = TestClient(app)
# Valid item
resp = client.get("/items/1")
print(f"200: {resp.json()}")
# Negative ID -> 400
resp = client.get("/items/-1")
print(f"400: {resp.status_code} {resp.json()}")
# Not found -> 404 with structured detail
resp = client.get("/items/999")
print(f"404: {resp.status_code} {resp.json()}")
# Delete not found -> custom header
resp = client.delete("/items/999")
print(f"Header: {resp.headers.get('X-Error-Code')}")
- •HTTPException immediately stops execution and returns a JSON error response
- •detail can be a string, dict, or list -- it is JSON-serialized automatically
- •Use status module constants (status.HTTP_404_NOT_FOUND) for readability
- •The optional headers parameter adds custom HTTP headers to the error response
- •HTTPException is raised, not returned -- use raise, not return
- •detail is the only field in the default JSON response body -- for richer error formats, use custom exception handlers
- •FastAPI's HTTPException extends Starlette's and adds the detail parameter to accept any JSON-serializable type, not just strings