Back to Blog
Python

python pydantic v1 vs v2: Key Differences and Migration

Compare python pydantic v1 vs v2: API changes, validation behavior, performance, and a practical migration path for existing projects.

PydanticData ValidationPython MigrationType HintsSerialization
Side-by-side comparison of Pydantic v1 and v2 API methods, with a migration arrow between them.

python pydantic v1 vs v2 requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When migrating from python pydantic v1 to v2, the most immediate change is the API surface for parsing and serialization. Methods like parse_obj and dict are replaced by model_validate and model_dump. This is not a cosmetic rename; the new methods align with the v2 core architecture and enforce stricter validation semantics. Understanding these differences is critical for a smooth upgrade.

What Changed in the Core API

The v2 API renames several commonly used methods and changes how configuration is declared. The table below shows the most frequent replacements:

v1 Method / Attributev2 ReplacementNotes
parse_objmodel_validateAccepts a dict or object; raises ValidationError on failure
parse_rawmodel_validate_jsonParses JSON directly
dictmodel_dumpReturns a dict; use model_dump_json for JSON string
jsonmodel_dump_jsonSerializes to JSON string
schemamodel_json_schemaGenerates JSON Schema
Config classmodel_config dictUse ConfigDict for type hints
@validator@field_validatorNew decorator with different signature
@root_validator@model_validatorNow supports mode before/after
orm_modefrom_attributesSet in model_config

For example, in v1 you might write:

from pydantic import BaseModel class User(BaseModel): name: str age: int user = User.parse_obj({"name": "Alice", "age": 30}) data = user.dict()

In v2, the equivalent is:

from pydantic import BaseModel class User(BaseModel): name: str age: int user = User.model_validate({"name": "Alice", "age": 30}) data = user.model_dump()

The old methods still exist in v2 but are deprecated and may raise warnings. They will be removed in a future release, so updating calls is a necessary part of migration.

Validation and Type Handling Differences

Pydantic v2 introduces a more consistent validation engine, but it also changes some default behaviors. One notable difference is how strict type coercion works. In v1, a string like "123" could be coerced to an integer in many cases. In v2, this coercion still happens in lax mode, but the rules are more predictable and some edge cases are stricter.

For instance, v2 no longer silently converts bool to int in all situations. If you have a field annotated as int, passing True will fail in v2 unless you explicitly allow it via model_config = ConfigDict(coerce_numbers_to_str=True) or use Field(strict=False). The default is lax, but the behavior is more aligned with Python's type system.

Another change is the handling of Optional fields. In v1, a field with Optional[str] would accept None and also an empty string. In v2, the same applies, but the validation error messages are more precise. The @field_validator decorator also changes how you access the field name and value:

from pydantic import BaseModel, field_validator class Product(BaseModel): name: str price: float @field_validator('price') @classmethod def check_positive(cls, v): if v <= 0: raise ValueError('price must be positive') return v

In v1, you used @validator('price') and the method could be an instance method. In v2, the decorator expects a classmethod and the first argument is the value, not the class. This is a breaking change that affects many existing models.

Performance and Runtime Behavior

Pydantic v2's core validation is implemented in Rust, which gives it a significant speed advantage over the pure-Python v1 engine. The performance gain comes from reducing the overhead of Python function calls during validation and using optimized data structures. While exact numbers depend on your schema complexity, the underlying mechanism is that v2 compiles validators into a fast execution path.

This improvement matters in high-throughput scenarios such as parsing API requests, validating configuration files, or processing large batches of data. In v1, each field validation involved multiple Python-level checks; v2 reduces that to a more direct path. The memory footprint is also lower because v2 avoids creating intermediate Python objects when possible.

If you are running a service that validates thousands of objects per second, upgrading to v2 can reduce CPU usage and latency. However, you should not rely on micro-benchmarks; instead, profile your own workload after migration to confirm the benefit.

Migration Path from v1 to v2

A practical migration starts with upgrading the package and running your test suite. Pydantic v2 provides a compatibility layer, but it is not a drop-in replacement. The recommended approach is to use the pydantic.v1 namespace if you need to keep v1 code temporarily, but that is not a long-term solution.

For most projects, you can automate the mechanical changes with a script that replaces method names and decorators. For example, replace parse_obj with model_validate, dict with model_dump, and @validator with @field_validator. Then adjust the Config class to model_config = ConfigDict(...).

Here is a before-and-after example for a model with custom config:

# v1 from pydantic import BaseModel, validator class Settings(BaseModel): debug: bool = False class Config: orm_mode = True @validator('debug') def check_debug(cls, v): return v
# v2 from pydantic import BaseModel, ConfigDict, field_validator class Settings(BaseModel): model_config = ConfigDict(from_attributes=True) debug: bool = False @field_validator('debug') @classmethod def check_debug(cls, v): return v

After updating the code, run your tests and address any validation errors that surface. The error messages in v2 are more detailed, which helps pinpoint where the old behavior was too permissive.

Handling Edge Cases and Compatibility

Some v1 features have no direct v2 equivalent. For example, @root_validator with skip_on_failure is now @model_validator(mode='before') or mode='after'. The allow_population_by_field_name config is now populate_by_name. These changes require manual review.

Another common issue is the handling of Union types. In v1, the order of types in a Union mattered because validation tried each type in order. In v2, the validator uses a smarter smart-mode that may choose a different type based on the input. This can lead to different results for ambiguous inputs. For instance, a field typed as Union[int, str] will now parse "123" as a string instead of an integer, because the string type is a more precise match. In v1, it would have tried int first and converted it.

If you rely on the old behavior, you can use Field(union_mode='left_to_right') to preserve v1 semantics. This is a key compatibility consideration for existing code.

Choosing v1 or v2 for a New Project

For any new Python project, v2 is the clear choice. It is actively maintained, faster, and has a cleaner API. The v1 branch is in maintenance mode and will not receive new features. If you are starting fresh, you should use v2 from the beginning and design your models with the new API in mind.

If you have a large existing codebase, the decision is more nuanced. You can run v1 and v2 side-by-side by importing pydantic.v1 for legacy modules, but this adds dependency complexity. A better strategy is to migrate incrementally: update models one by one, using the compatibility layer to catch issues, and then remove the v1 import once all models are converted.

One practical tip is to use the model_config dict for all configuration and to enable strict mode selectively where you need it. This reduces surprises and makes the validation behavior explicit. The migration effort is usually proportional to the number of custom validators and config options you have. For simple models, the change is mostly mechanical; for complex ones, you need to test edge cases carefully.

Pydantic v2 also introduces new features like TypeAdapter for validating non-model types, and ValidationInfo for accessing context in validators. These are worth exploring after migration, as they can simplify code that previously required workarounds in v1.

python pydantic v1 vs v2: Key Differences and Migration | RYUSLOG DEV