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.

Pydantic v1 to v2 API mapping Three v1 constructs map to their v2 replacements: the validator decorator becomes field_validator, class Config becomes model_config, and the dict method becomes model_dump. Pydantic v1 Pydantic v2 @validator / @root_validator class Config .dict() / .json() @field_validator / @model_validator model_config = ConfigDict(...) .model_dump() / .model_dump_json()
The migration is largely a rename of three constructs — validators, configuration, and serialization methods — with semantic changes to review case by case.

Core Mechanics: What Changed

The headline changes are mechanical renames with a few semantic shifts:

  • @validator@field_validator (with mode="before"|"after" replacing pre=True).
  • @root_validator@model_validator (operates on the model in after mode).
  • class Configmodel_config = ConfigDict(...).
  • .dict().model_dump(), .json().model_dump_json().
  • Field(..., regex=...)Field(..., pattern=...), and several other Field argument 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. regex and a few others changed; the codemod catches most, but review the diff.
  • Custom serialization. v1 json_encoders patterns change; use field serializers or model_serializer in v2.