FastAPI 0.115 to 0.120: New Lifespan Events Replace on_startup and on_shutdown
In FastAPI 0.115 and later, the @app.on_event(“startup”) and @app.on_event(“shutdown”) decorators are deprecated. The new lifespan parameter, powered by an async context manager, provides a more reliable way to handle application startup and shutdown. We’ll walk through the reasons for this change, compare the old and new approaches, and outline the migration process.
The Original on_event Decorators
Before FastAPI 0.115, we used @app.on_event decorators to register async functions for startup and shutdown events.
from fastapi import FastAPI
app = FastAPI()
models = {}
@app.on_event("startup")
async def startup():
models["ml"] = load_ml_model() # DB/ML init
@app.on_event("shutdown")
async def shutdown():
models.clear() # Cleanup
@app.get("/predict")
async def predict():
return {"result": models["ml"].predict()}
As you can see, we load the model on startup and clean up on shutdown. Though, there are limitations: no guaranteed cleanup (no yield mechanism), it only runs on the main app (sub-apps ignored), and memory leaks can occur with uvicorn —reload.
The New Lifespan Approach (0.115+)
With the new approach, we pass a lifespan parameter—an async context manager—to the FastAPI constructor. Code before the yield executes on startup; code after, on shutdown.
from contextlib import asynccontextmanager
from fastapi import FastAPI
models = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup: Load shared resources
models["ml"] = load_ml_model()
yield # App runs here
# Shutdown: Cleanup
models["ml"].close()
models.clear()
app = FastAPI(lifespan=lifespan)
| Aspect | Old on_event | New lifespan |
|---|---|---|
| Async Mgr | No | Yes (yield) |
| Sub-apps | Runs main only | Main only |
| Uvicorn Reload | Leaks | Safe |
| Deprec? | Yes 0.115+ | No |
| DB/ML Fit | Manual | Context-safe |
Migration Steps
- Audit your codebase for on_event usage: grep -r “@app.on_event” .
- Replace the decorators with a lifespan context manager:
# OLD @app.on_event("startup") async def load_db(): engine = create_engine(app.config["DB_URL"]) # NEW → lifespan @asynccontextmanager async def lifespan(app: FastAPI): engine = create_engine(app.config["DB_URL"]) yield engine.dispose() app = FastAPI(lifespan=lifespan) - Test: Run
pytestand start the server withuvicorn --reloadto verify reload behavior. - Upgrade FastAPI:
pip install "fastapi>=0.115" - Verify: Check logs for proper startup and shutdown order.
Real-World: DB Pool + Alembic
from sqlalchemy import create_engine
from alembic import command
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup DB
engine = create_engine(app.config["DATABASE_URL"])
with engine.connect() as conn:
conn.execute(text("CREATE SCHEMA IF NOT EXISTS app"))
command.upgrade(engine, "head") # Migrations
app.state.engine = engine
yield
# Shutdown
app.state.engine.dispose()
app = FastAPI(lifespan=lifespan)
@app.get("/health")
async def health():
with app.state.engine.connect() as conn:
return {"status": conn.scalar(text("SELECT 1"))}
Pytest example:
import pytest
from httpx import AsyncClient
from main import app
@pytest.fixture
async def client():
async with AsyncClient(app=app, base_url="http://test") as ac:
yield ac
@pytest.mark.asyncio
async def test_health(client):
resp = await client.get("/health")
assert resp.status_code == 200
Uvicorn/Gunicorn Notes
--reload: Lifespan restarts cleanly (no leaks).- Gunicorn
gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker - Sub-apps (
app.mount): Lifespan main-only, mount startup separate.
Why Migrate Now?
- Future-proof: The on_event decorators will be removed in a future version.
- Reliable cleanup: The yield ensures shutdown code runs even on exceptions.
- Performance: Shared state like connection pools is managed efficiently.
- Docs: FastAPI Lifespan
Questions? Comment below or FastAPI Discord.
<RelatedLinks {relatedLinks} />
Sponsored by Durable Programming
Need help maintaining or upgrading your Python application? Durable Programming specializes in keeping Python apps secure, performant, and up-to-date.
Hire Durable Programming