FastAPI supports custom response classes, among them there is support for multiple JSON response implementations. Default is JSONResponse but orjson and ujson are available as well. Both have their benchmark / performance testclaiminig they are the fastest, but worths checking for the given usecase.
Note: FastAPI supports different response classes, but request parsing is done by Starlette where you don’t have control over which JSON implementation to be used
CI run 29770319196 — Python 3.14, Ubuntu latest.
By default, FastAPI uses the base JSON implementation, let’s see the results:
| Test attribute | Test run 1 | Test run 2 | Test run 3 | Average |
|---|---|---|---|---|
| Requests per second | 19.51 | 19.64 | 19.62 | 19.59 |
| Time per request [ms] | 5125.68 | 5091.15 | 5096.26 | 5104.36 |
Note: There are some specialities requre attention
| Test attribute | Test run 1 | Test run 2 | Test run 3 | Average | Difference to baseline |
|---|---|---|---|---|---|
| Requests per second | 21.29 | 20.2 | 21.62 | 21.0367 | +7.38 % |
| Time per request [ms] | 4696.16 | 4951.17 | 4624.58 | 4757.3 | 347.06 ms |
Note: Just like orjson this has its own speciality
| Test attribute | Test run 1 | Test run 2 | Test run 3 | Average | Difference to baseline |
|---|---|---|---|---|---|
| Requests per second | 19.52 | 19.57 | 19.43 | 19.5067 | -0.43 % |
| Time per request [ms] | 5122.47 | 5108.58 | 5145.63 | 5125.56 | -21.2 ms |
CI run 29858316413 — Python 3.14, Ubuntu latest.
The same JSON response class comparison was repeated across 3 key server runners (Gunicorn 2 workers 0 threads, Uvicorn single-process, FastAPI CLI 1 worker) to see whether the choice of response class interacts with the runner type.
/sync/big_json_response/ — 1MB payload)| Runner | JSONResponse (baseline) | ORJSONResponse | UJSONResponse |
|---|---|---|---|
| Gunicorn (2 workers, 0 threads) | 17.65 RPS | 18.44 RPS (+4.46%) | 17.26 RPS (-2.19%) |
| Uvicorn single | 9.15 RPS | 10.36 RPS (+13.26%) | 9.40 RPS (+2.81%) |
| FastAPI CLI (1 worker) | 9.45 RPS | 10.66 RPS (+12.81%) | 9.84 RPS (+4.13%) |
/async/big_json_response/ — 1MB payload)| Runner | JSONResponse (baseline) | ORJSONResponse | UJSONResponse |
|---|---|---|---|
| Gunicorn (2 workers, 0 threads) | 17.06 RPS | 18.15 RPS (+6.39%) | 17.30 RPS (+1.41%) |
| Uvicorn single | 9.16 RPS | 10.38 RPS (+13.35%) | 9.29 RPS (+1.42%) |
| FastAPI CLI (1 worker) | 9.48 RPS | 10.58 RPS (+11.64%) | 9.74 RPS (+2.74%) |
Individual impact: +4-13% throughput by switching from JSONResponse to ORJSONResponse.
Please note that you can have different JSON response class for each API endpoint as shown in the FastAPI docs:
from fastapi import FastAPI
from fastapi.responses import UJSONResponse
app = FastAPI()
@app.get("/items/", response_class=UJSONResponse)
async def read_items():
return [{"item_id": "Foo"}]