]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
🗑️ Deprecate `ORJSONResponse` and `UJSONResponse` (#14964)
authorSebastián Ramírez <tiangolo@gmail.com>
Sun, 22 Feb 2026 16:34:59 +0000 (08:34 -0800)
committerGitHub <noreply@github.com>
Sun, 22 Feb 2026 16:34:59 +0000 (17:34 +0100)
docs/en/docs/reference/responses.md
fastapi/responses.py
pyproject.toml
tests/test_deprecated_responses.py [new file with mode: 0644]
tests/test_orjson_response_class.py
tests/test_tutorial/test_custom_response/test_tutorial001.py
tests/test_tutorial/test_custom_response/test_tutorial001b.py
uv.lock

index bd578612945b5ab1595254003456d38c626c9047..2df53e9701797547c40541c7b32561da55231802 100644 (file)
@@ -22,7 +22,13 @@ from fastapi.responses import (
 
 ## FastAPI Responses
 
-There are a couple of custom FastAPI response classes, you can use them to optimize JSON performance.
+There were a couple of custom FastAPI response classes that were intended to optimize JSON performance.
+
+However, they are now deprecated as you will now get better performance by using a [Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/).
+
+That way, Pydantic will serialize the data into JSON bytes on the Rust side, which will achieve better performance than these custom JSON responses.
+
+Read more about it in [Custom Response - HTML, Stream, File, others - `orjson` or Response Model](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model).
 
 ::: fastapi.responses.UJSONResponse
     options:
index 6c8db6f3353fffa953aa8efdd89739e2bda4c476..5b1154c046b503118dd421d152bbef221d76284c 100644 (file)
@@ -1,5 +1,6 @@
 from typing import Any
 
+from fastapi.exceptions import FastAPIDeprecationWarning
 from starlette.responses import FileResponse as FileResponse  # noqa
 from starlette.responses import HTMLResponse as HTMLResponse  # noqa
 from starlette.responses import JSONResponse as JSONResponse  # noqa
@@ -7,6 +8,7 @@ from starlette.responses import PlainTextResponse as PlainTextResponse  # noqa
 from starlette.responses import RedirectResponse as RedirectResponse  # noqa
 from starlette.responses import Response as Response  # noqa
 from starlette.responses import StreamingResponse as StreamingResponse  # noqa
+from typing_extensions import deprecated
 
 try:
     import ujson
@@ -20,12 +22,29 @@ except ImportError:  # pragma: nocover
     orjson = None  # type: ignore
 
 
+@deprecated(
+    "UJSONResponse is deprecated, FastAPI now serializes data directly to JSON "
+    "bytes via Pydantic when a return type or response model is set, which is "
+    "faster and doesn't need a custom response class. Read more in the FastAPI "
+    "docs: https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model "
+    "and https://fastapi.tiangolo.com/tutorial/response-model/",
+    category=FastAPIDeprecationWarning,
+    stacklevel=2,
+)
 class UJSONResponse(JSONResponse):
-    """
-    JSON response using the high-performance ujson library to serialize data to JSON.
+    """JSON response using the ujson library to serialize data to JSON.
+
+    **Deprecated**: `UJSONResponse` is deprecated. FastAPI now serializes data
+    directly to JSON bytes via Pydantic when a return type or response model is
+    set, which is faster and doesn't need a custom response class.
+
+    Read more in the
+    [FastAPI docs for Custom Response](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model)
+    and the
+    [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
 
-    Read more about it in the
-    [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/).
+    **Note**: `ujson` is not included with FastAPI and must be installed
+    separately, e.g. `pip install ujson`.
     """
 
     def render(self, content: Any) -> bytes:
@@ -33,12 +52,29 @@ class UJSONResponse(JSONResponse):
         return ujson.dumps(content, ensure_ascii=False).encode("utf-8")
 
 
+@deprecated(
+    "ORJSONResponse is deprecated, FastAPI now serializes data directly to JSON "
+    "bytes via Pydantic when a return type or response model is set, which is "
+    "faster and doesn't need a custom response class. Read more in the FastAPI "
+    "docs: https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model "
+    "and https://fastapi.tiangolo.com/tutorial/response-model/",
+    category=FastAPIDeprecationWarning,
+    stacklevel=2,
+)
 class ORJSONResponse(JSONResponse):
-    """
-    JSON response using the high-performance orjson library to serialize data to JSON.
+    """JSON response using the orjson library to serialize data to JSON.
+
+    **Deprecated**: `ORJSONResponse` is deprecated. FastAPI now serializes data
+    directly to JSON bytes via Pydantic when a return type or response model is
+    set, which is faster and doesn't need a custom response class.
+
+    Read more in the
+    [FastAPI docs for Custom Response](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model)
+    and the
+    [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/).
 
-    Read more about it in the
-    [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/).
+    **Note**: `orjson` is not included with FastAPI and must be installed
+    separately, e.g. `pip install orjson`.
     """
 
     def render(self, content: Any) -> bytes:
index c51eb8ce9bd5060e7b1a9fb9651239f28b1a758e..79dfc1fd35fa87b582217d5c21f110c41b1adee3 100644 (file)
@@ -105,10 +105,6 @@ all = [
     "itsdangerous >=1.1.0",
     # For Starlette's schema generation, would not be used with FastAPI
     "pyyaml >=5.3.1",
-    # For UJSONResponse
-    "ujson >=5.8.0",
-    # For ORJSONResponse
-    "orjson >=3.9.3",
     # To validate email fields
     "email-validator >=2.0.0",
     # Uvicorn with uvloop
@@ -151,6 +147,10 @@ docs = [
 docs-tests = [
     "httpx >=0.23.0,<1.0.0",
     "ruff >=0.14.14",
+    # For UJSONResponse
+    "ujson >=5.8.0",
+    # For ORJSONResponse
+    "orjson >=3.9.3",
 ]
 github-actions = [
     "httpx >=0.27.0,<1.0.0",
diff --git a/tests/test_deprecated_responses.py b/tests/test_deprecated_responses.py
new file mode 100644 (file)
index 0000000..eff5792
--- /dev/null
@@ -0,0 +1,73 @@
+import warnings
+
+import pytest
+from fastapi import FastAPI
+from fastapi.exceptions import FastAPIDeprecationWarning
+from fastapi.responses import ORJSONResponse, UJSONResponse
+from fastapi.testclient import TestClient
+from pydantic import BaseModel
+
+
+class Item(BaseModel):
+    name: str
+    price: float
+
+
+# ORJSON
+
+
+def _make_orjson_app() -> FastAPI:
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", FastAPIDeprecationWarning)
+        app = FastAPI(default_response_class=ORJSONResponse)
+
+    @app.get("/items")
+    def get_items() -> Item:
+        return Item(name="widget", price=9.99)
+
+    return app
+
+
+def test_orjson_response_returns_correct_data():
+    app = _make_orjson_app()
+    client = TestClient(app)
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", FastAPIDeprecationWarning)
+        response = client.get("/items")
+    assert response.status_code == 200
+    assert response.json() == {"name": "widget", "price": 9.99}
+
+
+def test_orjson_response_emits_deprecation_warning():
+    with pytest.warns(FastAPIDeprecationWarning, match="ORJSONResponse is deprecated"):
+        ORJSONResponse(content={"hello": "world"})
+
+
+# UJSON
+
+
+def _make_ujson_app() -> FastAPI:
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", FastAPIDeprecationWarning)
+        app = FastAPI(default_response_class=UJSONResponse)
+
+    @app.get("/items")
+    def get_items() -> Item:
+        return Item(name="widget", price=9.99)
+
+    return app
+
+
+def test_ujson_response_returns_correct_data():
+    app = _make_ujson_app()
+    client = TestClient(app)
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", FastAPIDeprecationWarning)
+        response = client.get("/items")
+    assert response.status_code == 200
+    assert response.json() == {"name": "widget", "price": 9.99}
+
+
+def test_ujson_response_emits_deprecation_warning():
+    with pytest.warns(FastAPIDeprecationWarning, match="UJSONResponse is deprecated"):
+        UJSONResponse(content={"hello": "world"})
index 6fe62daf97b4e023e5879d5ef292a7a8ceb7612d..63ea054d1f1469450ba19ab0046a85c0d9a92dbf 100644 (file)
@@ -1,9 +1,14 @@
+import warnings
+
 from fastapi import FastAPI
+from fastapi.exceptions import FastAPIDeprecationWarning
 from fastapi.responses import ORJSONResponse
 from fastapi.testclient import TestClient
 from sqlalchemy.sql.elements import quoted_name
 
-app = FastAPI(default_response_class=ORJSONResponse)
+with warnings.catch_warnings():
+    warnings.simplefilter("ignore", FastAPIDeprecationWarning)
+    app = FastAPI(default_response_class=ORJSONResponse)
 
 
 @app.get("/orjson_non_str_keys")
@@ -16,6 +21,8 @@ client = TestClient(app)
 
 
 def test_orjson_non_str_keys():
-    with client:
-        response = client.get("/orjson_non_str_keys")
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", FastAPIDeprecationWarning)
+        with client:
+            response = client.get("/orjson_non_str_keys")
     assert response.json() == {"msg": "Hello World", "1": 1}
index cec5ebe6cb80bcccc7853d9e413f48aa7f451ae9..a691dd3a84d29523c6e6e0c0d8de994655456668 100644 (file)
@@ -17,12 +17,14 @@ def get_client(request: pytest.FixtureRequest):
     return client
 
 
+@pytest.mark.filterwarnings("ignore::fastapi.exceptions.FastAPIDeprecationWarning")
 def test_get_custom_response(client: TestClient):
     response = client.get("/items/")
     assert response.status_code == 200, response.text
     assert response.json() == [{"item_id": "Foo"}]
 
 
+@pytest.mark.filterwarnings("ignore::fastapi.exceptions.FastAPIDeprecationWarning")
 def test_openapi_schema(client: TestClient):
     response = client.get("/openapi.json")
     assert response.status_code == 200, response.text
index 32437db86b757f558dad2ee7cfc807e6d4adcad2..11ce813b764b67fbf485f41580f0e471ed278b47 100644 (file)
@@ -1,17 +1,25 @@
+import warnings
+
+import pytest
+from fastapi.exceptions import FastAPIDeprecationWarning
 from fastapi.testclient import TestClient
 from inline_snapshot import snapshot
 
-from docs_src.custom_response.tutorial001b_py310 import app
+with warnings.catch_warnings():
+    warnings.simplefilter("ignore", FastAPIDeprecationWarning)
+    from docs_src.custom_response.tutorial001b_py310 import app
 
 client = TestClient(app)
 
 
+@pytest.mark.filterwarnings("ignore::fastapi.exceptions.FastAPIDeprecationWarning")
 def test_get_custom_response():
     response = client.get("/items/")
     assert response.status_code == 200, response.text
     assert response.json() == [{"item_id": "Foo"}]
 
 
+@pytest.mark.filterwarnings("ignore::fastapi.exceptions.FastAPIDeprecationWarning")
 def test_openapi_schema():
     response = client.get("/openapi.json")
     assert response.status_code == 200, response.text
diff --git a/uv.lock b/uv.lock
index 15ca8714f67ee4f1d53010f5ba5d317f38bb34bf..0d16c930b166c17ae5dedd711e5c089e9cc5e4f0 100644 (file)
--- a/uv.lock
+++ b/uv.lock
@@ -1083,12 +1083,10 @@ all = [
     { name = "httpx" },
     { name = "itsdangerous" },
     { name = "jinja2" },
-    { name = "orjson" },
     { name = "pydantic-extra-types" },
     { name = "pydantic-settings" },
     { name = "python-multipart" },
     { name = "pyyaml" },
-    { name = "ujson" },
     { name = "uvicorn", extra = ["standard"] },
 ]
 standard = [
@@ -1134,6 +1132,7 @@ dev = [
     { name = "mkdocs-redirects" },
     { name = "mkdocstrings", extra = ["python"] },
     { name = "mypy" },
+    { name = "orjson" },
     { name = "pillow" },
     { name = "playwright" },
     { name = "prek" },
@@ -1151,6 +1150,7 @@ dev = [
     { name = "typer" },
     { name = "types-orjson" },
     { name = "types-ujson" },
+    { name = "ujson" },
 ]
 docs = [
     { name = "black" },
@@ -1165,15 +1165,19 @@ docs = [
     { name = "mkdocs-material" },
     { name = "mkdocs-redirects" },
     { name = "mkdocstrings", extra = ["python"] },
+    { name = "orjson" },
     { name = "pillow" },
     { name = "python-slugify" },
     { name = "pyyaml" },
     { name = "ruff" },
     { name = "typer" },
+    { name = "ujson" },
 ]
 docs-tests = [
     { name = "httpx" },
+    { name = "orjson" },
     { name = "ruff" },
+    { name = "ujson" },
 ]
 github-actions = [
     { name = "httpx" },
@@ -1192,6 +1196,7 @@ tests = [
     { name = "httpx" },
     { name = "inline-snapshot" },
     { name = "mypy" },
+    { name = "orjson" },
     { name = "pwdlib", extra = ["argon2"] },
     { name = "pyjwt" },
     { name = "pytest" },
@@ -1202,6 +1207,7 @@ tests = [
     { name = "strawberry-graphql" },
     { name = "types-orjson" },
     { name = "types-ujson" },
+    { name = "ujson" },
 ]
 translations = [
     { name = "gitpython" },
@@ -1225,7 +1231,6 @@ requires-dist = [
     { name = "jinja2", marker = "extra == 'all'", specifier = ">=3.1.5" },
     { name = "jinja2", marker = "extra == 'standard'", specifier = ">=3.1.5" },
     { name = "jinja2", marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=3.1.5" },
-    { name = "orjson", marker = "extra == 'all'", specifier = ">=3.9.3" },
     { name = "pydantic", specifier = ">=2.7.0" },
     { name = "pydantic-extra-types", marker = "extra == 'all'", specifier = ">=2.0.0" },
     { name = "pydantic-extra-types", marker = "extra == 'standard'", specifier = ">=2.0.0" },
@@ -1240,7 +1245,6 @@ requires-dist = [
     { name = "starlette", specifier = ">=0.40.0,<1.0.0" },
     { name = "typing-extensions", specifier = ">=4.8.0" },
     { name = "typing-inspection", specifier = ">=0.4.2" },
-    { name = "ujson", marker = "extra == 'all'", specifier = ">=5.8.0" },
     { name = "uvicorn", extras = ["standard"], marker = "extra == 'all'", specifier = ">=0.12.0" },
     { name = "uvicorn", extras = ["standard"], marker = "extra == 'standard'", specifier = ">=0.12.0" },
     { name = "uvicorn", extras = ["standard"], marker = "extra == 'standard-no-fastapi-cloud-cli'", specifier = ">=0.12.0" },
@@ -1269,6 +1273,7 @@ dev = [
     { name = "mkdocs-redirects", specifier = ">=1.2.1,<1.3.0" },
     { name = "mkdocstrings", extras = ["python"], specifier = ">=0.30.1" },
     { name = "mypy", specifier = ">=1.14.1" },
+    { name = "orjson", specifier = ">=3.9.3" },
     { name = "pillow", specifier = ">=11.3.0" },
     { name = "playwright", specifier = ">=1.57.0" },
     { name = "prek", specifier = ">=0.2.22" },
@@ -1286,6 +1291,7 @@ dev = [
     { name = "typer", specifier = ">=0.21.1" },
     { name = "types-orjson", specifier = ">=3.6.2" },
     { name = "types-ujson", specifier = ">=5.10.0.20240515" },
+    { name = "ujson", specifier = ">=5.8.0" },
 ]
 docs = [
     { name = "black", specifier = ">=25.1.0" },
@@ -1300,15 +1306,19 @@ docs = [
     { name = "mkdocs-material", specifier = ">=9.7.0" },
     { name = "mkdocs-redirects", specifier = ">=1.2.1,<1.3.0" },
     { name = "mkdocstrings", extras = ["python"], specifier = ">=0.30.1" },
+    { name = "orjson", specifier = ">=3.9.3" },
     { name = "pillow", specifier = ">=11.3.0" },
     { name = "python-slugify", specifier = ">=8.0.4" },
     { name = "pyyaml", specifier = ">=5.3.1,<7.0.0" },
     { name = "ruff", specifier = ">=0.14.14" },
     { name = "typer", specifier = ">=0.21.1" },
+    { name = "ujson", specifier = ">=5.8.0" },
 ]
 docs-tests = [
     { name = "httpx", specifier = ">=0.23.0,<1.0.0" },
+    { name = "orjson", specifier = ">=3.9.3" },
     { name = "ruff", specifier = ">=0.14.14" },
+    { name = "ujson", specifier = ">=5.8.0" },
 ]
 github-actions = [
     { name = "httpx", specifier = ">=0.27.0,<1.0.0" },
@@ -1327,6 +1337,7 @@ tests = [
     { name = "httpx", specifier = ">=0.23.0,<1.0.0" },
     { name = "inline-snapshot", specifier = ">=0.21.1" },
     { name = "mypy", specifier = ">=1.14.1" },
+    { name = "orjson", specifier = ">=3.9.3" },
     { name = "pwdlib", extras = ["argon2"], specifier = ">=0.2.1" },
     { name = "pyjwt", specifier = ">=2.9.0" },
     { name = "pytest", specifier = ">=9.0.0" },
@@ -1337,6 +1348,7 @@ tests = [
     { name = "strawberry-graphql", specifier = ">=0.200.0,<1.0.0" },
     { name = "types-orjson", specifier = ">=3.6.2" },
     { name = "types-ujson", specifier = ">=5.10.0.20240515" },
+    { name = "ujson", specifier = ">=5.8.0" },
 ]
 translations = [
     { name = "gitpython", specifier = ">=3.1.46" },