Pydantic V2 Migration Guide for FastAPI
Migrating to Pydantic v2 is an API change, not a drop-in upgrade: validators, configuration, and serialization all move to new names, and the payoff is a faster validation core and a cleaner validation model.
This topic is part of Advanced Pydantic Validation and Serialization. The migration touches every other topic in the section — validators, serialization, and performance — because all of them changed shape in v2.
Core Mechanics: What Changed
The headline changes are mechanical renames with a few semantic shifts:
@validator→@field_validator(withmode="before"|"after"replacingpre=True).@root_validator→@model_validator(operates on the model inaftermode).class Config→model_config = ConfigDict(...)..dict()→.model_dump(),.json()→.model_dump_json().Field(..., regex=...)→Field(..., pattern=...), and several otherFieldargument renames.
from pydantic import BaseModel, ConfigDict, field_validator
class Account(BaseModel):
model_config = ConfigDict(str_strip_whitespace=True) # was: class Config
email: str
@field_validator("email") # was: @validator("email")
@classmethod
def lower(cls, v: str) -> str:
return v.lower()
Production Implementation: A Phased Rollout
Migrate incrementally rather than in one risky commit. Run the bump-pydantic codemod to handle the mechanical renames, then review validators and coercion-sensitive code by hand. The full non-breaking sequence is in Migrating from Pydantic v1 to v2 Without Breaking APIs.
# Automate the mechanical renames, then review the diff carefully.
pip install bump-pydantic
bump-pydantic app/
Async and Performance Notes
The reason to migrate is largely performance: v2's Rust core validates and serializes substantially faster than v1's Python implementation, which matters most on hot paths and large payloads. After migrating, revisit the performance practices — single-pass serialization and model_construct — to capture the full benefit.
Testing Strategy
The safety net for a migration is a contract test suite that asserts request and response shapes. Run it against both versions during the transition so any behavioral drift — especially stricter coercion in v2 — surfaces immediately:
def test_response_contract_unchanged(client):
resp = client.get("/users/1")
# The serialized shape must match pre-migration output exactly.
assert set(resp.json()) == {"id", "email", "created_at"}
Failure Modes and Debugging
- Stricter coercion. v2 rejects some loose inputs v1 accepted (for example a string where an int is required). Audit boundary models against real traffic.
- Half-migrated modules. Mixing v1 and v2 styles causes confusing errors; migrate module by module and remove the compatibility imports.
- Renamed Field arguments.
regexand a few others changed; the codemod catches most, but review the diff. - Custom serialization. v1
json_encoderspatterns change; use field serializers ormodel_serializerin v2.
Related Reading
- Up to the section: Advanced Pydantic Validation and Serialization.
- Hands-on guide: Migrating from Pydantic v1 to v2 Without Breaking APIs.
- Composes with: Custom Validators and Field Constraints and Performance Optimization for Models.