FastAPI / uvicorn Exception in ASGI application (tested)

ERROR: Exception in ASGI application is uvicorn reporting that an exception escaped your FastAPI app; the client just gets a plain-text 500. The cause is at the bottom of the traceback: usually a bug in the endpoint, a ResponseValidationError, or a return value that can't be encoded as JSON.

1. Reproduce it: three ways to get the 500

The app (app.py), run with python -m uvicorn app:app:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class Out(BaseModel):
    id: int
    name: str
@app.get("/boom")
def boom():
    return {"ratio": 1 / 0}
@app.get("/http")
def http():
    raise HTTPException(status_code=404, detail="no such item")
@app.get("/model", response_model=Out)
def model():
    return {"id": 1}            # 'name' missing from the response
@app.get("/obj")
def obj():
    return object()

A client hitting each route:

import urllib.request, urllib.error
for path in ("/boom", "/http", "/model", "/obj"):
    try:
        r = urllib.request.urlopen("http://127.0.0.1:8000" + path)
        print(path, r.status, r.read().decode())
    except urllib.error.HTTPError as e:
        print(path, e.code, e.headers["content-type"], e.read().decode())

Output:

/boom 500 text/plain; charset=utf-8 Internal Server Error
/http 404 application/json {"detail":"no such item"}
/model 500 text/plain; charset=utf-8 Internal Server Error
/obj 500 text/plain; charset=utf-8 Internal Server Error

The client only ever sees a plain-text Internal Server Error. The real error is in the server log. Here it is with the ~60 lines of uvicorn/Starlette/FastAPI frames in each traceback cut out (everything else is verbatim):

INFO:     ... "GET /boom HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  ... (about 60 framework lines) ...
  File "/w/app.py", line 9, in boom
    return {"ratio": 1 / 0}
                     ~~^~~
ZeroDivisionError: division by zero
INFO:     ... "GET /http HTTP/1.1" 404 Not Found
INFO:     ... "GET /model HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  ... (about 60 framework lines) ...
fastapi.exceptions.ResponseValidationError: 1 validation error:
  {'type': 'missing', 'loc': ('response', 'name'), 'msg': 'Field required', 'input': {'id': 1}}
  File "/w/app.py", line 13, in model
INFO:     ... "GET /obj HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  ... (about 60 framework lines) ...
    return jsonable_encoder(response_content)
  File "/usr/local/lib/python3.14/site-packages/fastapi/encoders.py", line 355, in jsonable_encoder
    raise ValueError(errors) from e
ValueError: [TypeError("'object' object is not iterable"), TypeError('vars() argument must have __dict__ attribute')]

"Exception in ASGI application" is just uvicorn saying an exception escaped your app. Ignore the framework frames and read the last line (the exception) and the last File line that points into your own code. The three cases above are the common ones:

HTTPException is not an error in this sense: /http returned a clean 404 JSON response and logged only the access line, no traceback.

2. Return JSON instead of plain text, and log it your way

import logging
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.testclient import TestClient
log = logging.getLogger("app")
app = FastAPI()
@app.exception_handler(Exception)
async def unhandled(request: Request, exc: Exception):
    log.exception("unhandled error on %s", request.url.path)
    return JSONResponse({"detail": "internal error"}, status_code=500)
@app.get("/boom")
def boom():
    return {"ratio": 1 / 0}
c = TestClient(app, raise_server_exceptions=False)
r = c.get("/boom")
print(r.status_code, r.headers["content-type"], r.text)
try:
    TestClient(app).get("/boom")
except ZeroDivisionError as e:
    print("default TestClient re-raises:", repr(e))

Output:

500 application/json {"detail":"internal error"}
default TestClient re-raises: ZeroDivisionError('division by zero')

An @app.exception_handler(Exception) turns the plain-text 500 into a JSON one and gives you a place to log with your own logger (log.exception records the traceback). Note the second line: Starlette's TestClient re-raises server exceptions by default, so in tests you see the real exception directly; pass raise_server_exceptions=False to test the 500 response itself.

Checklist

Related: FastAPI 422 Unprocessable Entity, Pydantic "Field required".

Frequently asked questions

What does Exception in ASGI application mean?

uvicorn caught an unhandled exception from your app and returned a 500. The real error is the last line of the traceback in the server log.

What is ResponseValidationError in FastAPI?

Your endpoint's return value does not match its response_model; the loc, e.g. ('response', 'name'), shows the missing or wrong field.

How do I return JSON for FastAPI 500 errors?

Register @app.exception_handler(Exception) that logs the exception and returns JSONResponse(..., status_code=500).

Written with AI assistance. Measured on 2026-10-23 with FastAPI 0.161.1, uvicorn 0.54.0 and Python 3.14.7 (python:3-alpine in Docker). Blocks were run verbatim; the server log is unedited except for the framework frames marked as cut. PlainHeaders is our extension; Python, FastAPI and uvicorn are free.