Repository: pydantic/pydantic
Pull request: Improve alias configuration APIs
This PR introduces a few new features and a few changes. They might be easier to digest by checking out this simple API changes snippet:

```py
class ConfigDict:
    validate_by_alias: bool = True
    serialize_by_alias: bool = False
        in v3, serialize_by_alias default should change to True

    populate_by_name: bool = False ---> validate_by_name: bool = False

def model_dump_X(by_alias: bool = False, ...):
    in v3, by_alias default should change to True

def model_validate_X(by_alias: bool = True, by_name: bool = False, ...):
```

Here's the longer version:

1. `validate_by_alias` has been introduced as a `bool` type configuration flag, set to `True` by default.
2. `populate_by_name` has been deprecated in favor of `validate_by_name` (for consistency with `validate_by_alias`. This is set to `False` by default to match the behavior of `populate_by_name`. Though this is deprecated, we include a patch in the configuration init logic so that this setting still works in the short term. This will be removed in V3.

New feature capability: you can now set `validate_by_alias = False` and `validate_by_name = True` if you want to **only** allow validation by field name. This limitation was not possible with solely the `populate_by_name` configuration setting.

Note: You cannot set both `validate_by_name` and `validate_by_alias` to `False`. This results in a schema error.

3. `serialize_by_alias` has been introduced as a `bool` type configuration flag, set to `False` by default to match the `by_alias` setting on model serialization functions. We anticipate changing this default in V3 to be consistent with `validate_by_alias = True` by default.

Most of these changes are described in more detail, and practically implemented, in https://github.com/pydantic/pydantic-core/pull/1640.

This makes significant progress on https://github.com/pydantic/pydantic/issues/8379. Some changes (like default value changes) will have to wait for V3.

Fixes https://github.com/pydantic/pydantic/issues/10964.

Problem statement:
Unexpected UserWarning when serialize a model with PlainValidator to JSON
### Initial Checks

- [X] I confirm that I'm using Pydantic V2

### Description

When performing json-serialization of a model  with a `PlainValidator` on a field of `date` type，there is a `UserWarning` generated by the core which I do not think that it should be raised.  

With pydantic==2.8.2 and pydantic-core==2.20.1, this exception is not raised.

```
/home/hong/miniconda3/envs/tm/lib/python3.12/site-packages/pydantic/main.py:477: UserWarning: Pydantic serializer warnings:
  Expected `date` but got `str` with value `'2024-11-25'` - serialized value may not be as expected
  return self.__pydantic_serializer__.to_json(
{"birthdate":"2024-11-25"}
```


### Example Code

```Python
from datetime import date
from typing import Annotated

from pydantic import BaseModel, PlainValidator


class Person(BaseModel):
    birthdate: Annotated[
        date | None,
        PlainValidator(
            lambda v: v if isinstance(v, date) else None,
        ),
    ]


if __name__ == '__main__':
    me = Person(birthdate=date.today())
    print(me.model_dump_json())
```


### Python, Pydantic & OS Version

```Text
pydantic version: 2.10.1
        pydantic-core version: 2.27.1
          pydantic-core build: profile=release pgo=false
                 install path: /home/hong/miniconda3/envs/tm/lib/python3.12/site-packages/pydantic
               python version: 3.12.7 | packaged by Anaconda, Inc. | (main, Oct  4 2024, 13:27:36) [GCC 11.2.0]
                     platform: Linux-6.8.0-49-generic-x86_64-with-glibc2.39
             related packages: pydantic-settings-2.6.1 fastapi-0.104.1 typing_extensions-4.12.2 mypy-1.13.0
                       commit: unknown
```

Hints from issue discussion:
@hongyuan1306,

Thanks for reporting. This is definitely a bug. Seems like probably in `pydanic-core`.
Almost the same issue with PlainValidator was reported few months ago in #10402

Commit message: revert third party test change - will fix in another PR

Patch to review:
diff --git a/docs/concepts/alias.md b/docs/concepts/alias.md
index 3139e4b99ac..7f7fc5c4cc7 100644
--- a/docs/concepts/alias.md
+++ b/docs/concepts/alias.md
@@ -201,3 +201,229 @@ You may set `alias_priority` on a field to change this behavior:
 
 The same precedence applies to `validation_alias` and `serialization_alias`.
 See more about the different field aliases under [field aliases](../concepts/fields.md#field-aliases).
+
+## Alias Use Configuration
+
+You can use [`ConfigDict`][pydantic.config.ConfigDict] settings or runtime validation / serialization
+settings to control whether or not aliases are used.
+
+### `ConfigDict` Settings
+
+You can use [`ConfigDict`][pydantic.config.ConfigDict] settings to control, at the model level,
+whether or not aliases are used for validation and serialization. If you would like to control
+this behavior for nested models / surpassing the config-model boundary, use [runtime settings](#runtime-settings).
+
+#### Validation
+
+When validating data, you can enable population of attributes by attribute name, alias, or both.
+**By default**, Pydantic uses aliases for validation. Further configuration is available via:
+
+* [`ConfigDict.validate_by_alias`][pydantic.config.ConfigDict.validate_by_alias]: `True` by default
+* [`ConfigDict.validate_by_name`][pydantic.config.ConfigDict.validate_by_name]: `False` by default
+
+=== "`validate_by_alias`"
+
+    ```python
+    from pydantic import BaseModel, ConfigDict, Field
+
+
+    class Model(BaseModel):
+        my_field: str = Field(validation_alias='my_alias')
+
+        model_config = ConfigDict(validate_by_alias=True, validate_by_name=False)
+
+
+    print(repr(Model(my_alias='foo')))  # (1)!
+    #> Model(my_field='foo')
+    ```
+
+    1. The alias `my_alias` is used for validation.
+
+=== "`validate_by_name`"
+
+    ```python
+    from pydantic import BaseModel, ConfigDict, Field
+
+
+    class Model(BaseModel):
+        my_field: str = Field(validation_alias='my_alias')
+
+        model_config = ConfigDict(validate_by_alias=False, validate_by_name=True)
+
+
+    print(repr(Model(my_field='foo')))  # (1)!
+    #> Model(my_field='foo')
+    ```
+
+    1. the attribute identifier `my_field` is used for validation.
+
+=== "`validate_by_alias` and `validate_by_name`"
+
+    ```python
+    from pydantic import BaseModel, ConfigDict, Field
+
+
+    class Model(BaseModel):
+        my_field: str = Field(validation_alias='my_alias')
+
+        model_config = ConfigDict(validate_by_alias=True, validate_by_name=True)
+
+
+    print(repr(Model(my_alias='foo')))  # (1)!
+    #> Model(my_field='foo')
+
+    print(repr(Model(my_field='foo')))  # (2)!
+    #> Model(my_field='foo')
+    ```
+
+    1. The alias `my_alias` is used for validation.
+    2. the attribute identifier `my_field` is used for validation.
+
+!!! warning
+    You cannot set both `validate_by_alias` and `validate_by_name` to `False`.
+    A [user error](../errors/usage_errors.md#validate-by-alias-and-name-false) is raised in this case.
+
+#### Serialization
+
+When serializing data, you can enable serialization by alias, which is disabled by default.
+See the [`ConfigDict.serialize_by_alias`][pydantic.config.ConfigDict.serialize_by_alias] API documentation
+for an example.
+
+```python
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class Model(BaseModel):
+    my_field: str = Field(serialization_alias='my_alias')
+
+    model_config = ConfigDict(serialize_by_alias=True)
+
+
+m = Model(my_field='foo')
+print(m.model_dump())  # (1)!
+#> {'my_alias': 'foo'}
+```
+
+1. The alias `my_alias` is used for serialization.
+
+!!! note
+    The fact that serialization by alias is disabled by default is notably inconsistent with the default for
+    validation (where aliases are used by default). We anticipate changing this default in V3.
+
+### Runtime Settings
+
+You can use runtime alias flags to control alias use for validation and serialization
+on a per-call basis. If you would like to control this behavior on a model level, use
+[`ConfigDict` settings](#configdict-settings).
+
+#### Validation
+
+When validating data, you can enable population of attributes by attribute name, alias, or both.
+
+The `by_alias` and `by_name` flags are available on the [`model_validate`][pydantic.main.BaseModel.model_validate],
+[`model_validate_json`][pydantic.main.BaseModel.model_validate_json], and [`model_validate_strings`][pydantic.main.BaseModel.model_validate_strings] methods.
+
+By default:
+* `by_alias` is `True`
+* `by_name` is `False`
+
+These settings are also available on [`TypeAdapter`][pydantic.type_adapter.TypeAdapter] validation methods.
+
+=== "`by_alias`"
+
+    ```python
+    from pydantic import BaseModel, Field
+
+
+    class Model(BaseModel):
+        my_field: str = Field(validation_alias='my_alias')
+
+
+    m = Model.model_validate(
+        {'my_alias': 'foo'},  # (1)!
+        by_alias=True,
+        by_name=False,
+    )
+    print(repr(m))
+    #> Model(my_field='foo')
+    ```
+
+    1. The alias `my_alias` is used for validation.
+
+=== "`by_name`"
+
+    ```python
+    from pydantic import BaseModel, Field
+
+
+    class Model(BaseModel):
+        my_field: str = Field(validation_alias='my_alias')
+
+
+    m = Model.model_validate(
+        {'my_field': 'foo'}, by_alias=False, by_name=True  # (1)!
+    )
+    print(repr(m))
+    #> Model(my_field='foo')
+    ```
+
+    1. the attribute identifier `my_field` is used for validation.
+
+=== "`validate_by_alias` and `validate_by_name`"
+
+    ```python
+    from pydantic import BaseModel, Field
+
+
+    class Model(BaseModel):
+        my_field: str = Field(validation_alias='my_alias')
+
+
+    m = Model.model_validate(
+        {'my_alias': 'foo'}, by_alias=True, by_name=True  # (1)!
+    )
+    print(repr(m))
+    #> Model(my_field='foo')
+
+    m = Model.model_validate(
+        {'my_field': 'foo'}, by_alias=True, by_name=True  # (2)!
+    )
+    print(repr(m))
+    #> Model(my_field='foo')
+    ```
+
+    1. The alias `my_alias` is used for validation.
+    2. the attribute identifier `my_field` is used for validation.
+
+!!! warning
+    You cannot set both `by_alias` and `by_name` to `False`.
+    A [user error](../errors/usage_errors.md#validate-by-alias-and-name-false) is raised in this case.
+
+#### Serialization
+
+When serializing data, you can enable serialization by alias via the `by_alias` flag
+which is available on the [`model_dump`][pydantic.main.BaseModel.model_dump] and
+[`model_dump_json`][pydantic.main.BaseModel.model_dump_json] methods.
+
+By default, `by_alias` is `False`.
+
+These settings are also available on [`TypeAdapter`][pydantic.type_adapter.TypeAdapter] serialization methods.
+
+```py
+from pydantic import BaseModel, Field
+
+
+class Model(BaseModel):
+    my_field: str = Field(serialization_alias='my_alias')
+
+
+m = Model(my_field='foo')
+print(m.model_dump(by_alias=True))  # (1)!
+#> {'my_alias': 'foo'}
+```
+
+1. The alias `my_alias` is used for serialization.
+
+!!! note
+    The fact that serialization by alias is disabled by default is notably inconsistent with the default for
+    validation (where aliases are used by default). We anticipate changing this default in V3.
diff --git a/docs/concepts/fields.md b/docs/concepts/fields.md
index fb01d7e425f..dba44f6220b 100644
--- a/docs/concepts/fields.md
+++ b/docs/concepts/fields.md
@@ -221,7 +221,10 @@ print(user.model_dump(by_alias=True))  # (2)!
     Note that the `by_alias` keyword argument defaults to `False`, and must be specified explicitly to dump
     models using the field (serialization) aliases.
 
-    When `by_alias=True`, the alias `'username'` is also used during serialization.
+    You can also use [`ConfigDict.serialize_by_alias`][pydantic.config.ConfigDict.serialize_by_alias] to
+    configure this behavior at the model level.
+
+    When `by_alias=True`, the alias `'username'` used during serialization.
 
 If you want to use an alias _only_ for validation, you can use the `validation_alias` parameter:
 
@@ -287,16 +290,14 @@ print(user.model_dump(by_alias=True))  # (2)!
 
     1. Accepted by type checkers.
 
-    This means that when using the [`populate_by_name`][pydantic.config.ConfigDict.populate_by_name] model
-    setting (which allows both the field name and alias to be used during model validation), type checkers
-    will error when the actual field name is used:
+    This means that when using the [`validate_by_name`][pydantic.config.ConfigDict.validate_by_name] model setting (which allows both the field name and alias to be used during model validation), type checkers will error when the actual field name is used:
 
     ```python
     from pydantic import BaseModel, ConfigDict, Field
 
 
     class User(BaseModel):
-        model_config = ConfigDict(populate_by_name=True)
+        model_config = ConfigDict(validate_by_name=True)
 
         name: str = Field(alias='username')
 
@@ -316,7 +317,7 @@ print(user.model_dump(by_alias=True))  # (2)!
 
 
     class User(BaseModel):
-        model_config = ConfigDict(populate_by_name=True)
+        model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
 
         name: Annotated[str, Field(alias='username')]
 
diff --git a/docs/concepts/serialization.md b/docs/concepts/serialization.md
index 9e2d079241d..a4595f927e4 100644
--- a/docs/concepts/serialization.md
+++ b/docs/concepts/serialization.md
@@ -312,6 +312,8 @@ If you want to do this and still get proper type-checking for this method, you c
 `if TYPE_CHECKING:` block:
 
 ```python
+from __future__ import annotations
+
 from typing import TYPE_CHECKING, Any, Literal
 
 from pydantic import BaseModel, model_serializer
@@ -332,7 +334,7 @@ class Model(BaseModel):
             mode: Literal['json', 'python'] | str = 'python',
             include: Any = None,
             exclude: Any = None,
-            by_alias: bool = False,
+            by_alias: bool | None = False,
             exclude_unset: bool = False,
             exclude_defaults: bool = False,
             exclude_none: bool = False,
diff --git a/docs/errors/usage_errors.md b/docs/errors/usage_errors.md
index 75a75ed2ac3..151f24bd931 100644
--- a/docs/errors/usage_errors.md
+++ b/docs/errors/usage_errors.md
@@ -1381,3 +1381,25 @@ try:
 except PydanticUserError as exc_info:
     assert exc_info.code == 'invalid-self-type'
 ```
+
+## `validate_by_alias` and `validate_by_name` `False` {#validate-by-alias-and-name-false}
+
+This error is raised when you set `validate_by_alias` and `validate_by_name` to `False` in a model's configuration.
+
+This is not allowed because it would make it impossible to populate attributes.
+
+```python
+from pydantic import BaseModel, ConfigDict, Field, PydanticUserError
+
+try:
+
+    class Model(BaseModel):
+        a: int = Field(alias='A')
+
+        model_config = ConfigDict(
+            validate_by_alias=False, validate_by_name=False
+        )
+
+except PydanticUserError as exc_info:
+    assert exc_info.code == 'validate-by-alias-and-name-false'
+```
diff --git a/docs/integrations/mypy.md b/docs/integrations/mypy.md
index 3315d1bd1bb..19a77faad1b 100644
--- a/docs/integrations/mypy.md
+++ b/docs/integrations/mypy.md
@@ -88,7 +88,7 @@ that the version support policy is subject to change at discretion of contributo
 
 * Any required fields that don't have dynamically-determined aliases will be included as required
   keyword arguments.
-* If the [`populate_by_name`][pydantic.ConfigDict.populate_by_name] model configuration value is set to
+* If the [`validate_by_name`][pydantic.ConfigDict.validate_by_name] model configuration value is set to
   `True`, the generated signature will use the field names rather than aliases.
 * The [`init_forbid_extra`](#init_forbid_extra) and [`init_typed`](#init_typed) plugin configuration
   values can further fine-tune the synthesized `__init__` method.
@@ -196,7 +196,7 @@ For this reason, the plugin will add an extra `**kwargs: Any` parameter when syn
 ### `warn_required_dynamic_aliases`
 
 Whether to error when using a dynamically-determined alias or alias generator on a model with
-[`populate_by_name`][pydantic.ConfigDict.populate_by_name] set to `False`. If such aliases are
+[`validate_by_name`][pydantic.ConfigDict.validate_by_name] set to `False`. If such aliases are
 present, mypy cannot properly type check calls to `__init__`. In this case, it will default to
 treating all arguments as not required.
 
diff --git a/docs/migration.md b/docs/migration.md
index d18c229f890..c8bb23e528b 100644
--- a/docs/migration.md
+++ b/docs/migration.md
@@ -343,7 +343,7 @@ will be merged, and for any settings defined in both, those from `Model2` will o
     * `post_init_call`
 
 * The following config settings have been renamed:
-    * `allow_population_by_field_name` → `populate_by_name`
+    * `allow_population_by_field_name` → `validate_by_name`
     * `anystr_lower` → `str_to_lower`
     * `anystr_strip_whitespace` → `str_strip_whitespace`
     * `anystr_upper` → `str_to_upper`
diff --git a/pydantic/_internal/_config.py b/pydantic/_internal/_config.py
index 91f4609db44..45df16a8529 100644
--- a/pydantic/_internal/_config.py
+++ b/pydantic/_internal/_config.py
@@ -87,6 +87,9 @@ class ConfigWrapper:
     validation_error_cause: bool
     use_attribute_docstrings: bool
     cache_strings: bool | Literal['all', 'keys', 'none']
+    validate_by_alias: bool
+    validate_by_name: bool
+    serialize_by_alias: bool
 
     def __init__(self, config: ConfigDict | dict[str, Any] | type[Any] | None, *, check: bool = True):
         if check:
@@ -172,6 +175,19 @@ def core_config(self, title: str | None) -> core_schema.CoreConfig:
                 stacklevel=2,
             )
 
+        if (populate_by_name := config.get('populate_by_name')) is not None:
+            # We include this patch for backwards compatibility purposes, but this config setting will be officially removed in v3.0.
+            # Thus, the above warning and this patch can be removed then as well.
+            if config.get('validate_by_name') is None:
+                config['validate_by_alias'] = True
+                config['validate_by_name'] = populate_by_name
+
+        if (not config.get('validate_by_alias', True)) and (not config.get('validate_by_name', False)):
+            raise PydanticUserError(
+                'At least one of `validate_by_alias` or `validate_by_name` must be set to True.',
+                code='validate-by-alias-and-name-false',
+            )
+
         return core_schema.CoreConfig(
             **{  # pyright: ignore[reportArgumentType]
                 k: v
@@ -179,7 +195,6 @@ def core_config(self, title: str | None) -> core_schema.CoreConfig:
                     ('title', config.get('title') or title or None),
                     ('extra_fields_behavior', config.get('extra')),
                     ('allow_inf_nan', config.get('allow_inf_nan')),
-                    ('populate_by_name', config.get('populate_by_name')),
                     ('str_strip_whitespace', config.get('str_strip_whitespace')),
                     ('str_to_lower', config.get('str_to_lower')),
                     ('str_to_upper', config.get('str_to_upper')),
@@ -199,6 +214,9 @@ def core_config(self, title: str | None) -> core_schema.CoreConfig:
                     ('regex_engine', config.get('regex_engine')),
                     ('validation_error_cause', config.get('validation_error_cause')),
                     ('cache_strings', config.get('cache_strings')),
+                    ('validate_by_alias', config.get('validate_by_alias')),
+                    ('validate_by_name', config.get('validate_by_name')),
+                    ('serialize_by_alias', config.get('serialize_by_alias')),
                 )
                 if v is not None
             }
@@ -278,6 +296,9 @@ def push(self, config_wrapper: ConfigWrapper | ConfigDict | None):
     validation_error_cause=False,
     use_attribute_docstrings=False,
     cache_strings=True,
+    validate_by_alias=True,
+    validate_by_name=False,
+    serialize_by_alias=False,
 )
 
 
@@ -318,7 +339,7 @@ def prepare_config(config: ConfigDict | dict[str, Any] | type[Any] | None) -> Co
     'post_init_call',
 }
 V2_RENAMED_KEYS = {
-    'allow_population_by_field_name': 'populate_by_name',
+    'allow_population_by_field_name': 'validate_by_name',
     'anystr_lower': 'str_to_lower',
     'anystr_strip_whitespace': 'str_strip_whitespace',
     'anystr_upper': 'str_to_upper',
diff --git a/pydantic/_internal/_dataclasses.py b/pydantic/_internal/_dataclasses.py
index 8d0f47b758d..954ab32f234 100644
--- a/pydantic/_internal/_dataclasses.py
+++ b/pydantic/_internal/_dataclasses.py
@@ -153,7 +153,7 @@ def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) -
             # as it is the one synthesized by the stdlib `dataclass` module:
             init=original_init,
             fields=cls.__pydantic_fields__,  # type: ignore
-            populate_by_name=config_wrapper.populate_by_name,
+            validate_by_name=config_wrapper.validate_by_name,
             extra=config_wrapper.extra,
             is_dataclass=True,
         ),
diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py
index 647057175a3..9ec8a612a9e 100644
--- a/pydantic/_internal/_generate_schema.py
+++ b/pydantic/_internal/_generate_schema.py
@@ -1969,7 +1969,7 @@ def _call_schema(self, function: ValidateCallSupportedTypes) -> core_schema.Call
                 var_args_schema=var_args_schema,
                 var_kwargs_mode=var_kwargs_mode,
                 var_kwargs_schema=var_kwargs_schema,
-                populate_by_name=config_wrapper.populate_by_name,
+                validate_by_name=config_wrapper.validate_by_name,
             ),
             function,
             return_schema=return_schema,
diff --git a/pydantic/_internal/_model_construction.py b/pydantic/_internal/_model_construction.py
index 9293ff6c2d5..88f67ef5d75 100644
--- a/pydantic/_internal/_model_construction.py
+++ b/pydantic/_internal/_model_construction.py
@@ -638,7 +638,7 @@ def complete_model_class(
             generate_pydantic_signature,
             init=cls.__init__,
             fields=cls.__pydantic_fields__,
-            populate_by_name=config_wrapper.populate_by_name,
+            validate_by_name=config_wrapper.validate_by_name,
             extra=config_wrapper.extra,
         ),
     )
diff --git a/pydantic/_internal/_signature.py b/pydantic/_internal/_signature.py
index 2273577ccb2..977e5d29340 100644
--- a/pydantic/_internal/_signature.py
+++ b/pydantic/_internal/_signature.py
@@ -80,7 +80,7 @@ def _process_param_defaults(param: Parameter) -> Parameter:
 def _generate_signature_parameters(  # noqa: C901 (ignore complexity, could use a refactor)
     init: Callable[..., None],
     fields: dict[str, FieldInfo],
-    populate_by_name: bool,
+    validate_by_name: bool,
     extra: ExtraValues | None,
 ) -> dict[str, Parameter]:
     """Generate a mapping of parameter names to Parameter objects for a pydantic BaseModel or dataclass."""
@@ -107,7 +107,7 @@ def _generate_signature_parameters(  # noqa: C901 (ignore complexity, could use
         merged_params[param.name] = param
 
     if var_kw:  # if custom init has no var_kw, fields which are not declared in it cannot be passed through
-        allow_names = populate_by_name
+        allow_names = validate_by_name
         for field_name, field in fields.items():
             # when alias is a str it should be used for signature generation
             param_name = _field_name_for_signature(field_name, field)
@@ -164,7 +164,7 @@ def _generate_signature_parameters(  # noqa: C901 (ignore complexity, could use
 def generate_pydantic_signature(
     init: Callable[..., None],
     fields: dict[str, FieldInfo],
-    populate_by_name: bool,
+    validate_by_name: bool,
     extra: ExtraValues | None,
     is_dataclass: bool = False,
 ) -> Signature:
@@ -173,14 +173,14 @@ def generate_pydantic_signature(
     Args:
         init: The class init.
         fields: The model fields.
-        populate_by_name: The `populate_by_name` value of the config.
+        validate_by_name: The `validate_by_name` value of the config.
         extra: The `extra` value of the config.
         is_dataclass: Whether the model is a dataclass.
 
     Returns:
         The dataclass/BaseModel subclass signature.
     """
-    merged_params = _generate_signature_parameters(init, fields, populate_by_name, extra)
+    merged_params = _generate_signature_parameters(init, fields, validate_by_name, extra)
 
     if is_dataclass:
         merged_params = {k: _process_param_defaults(v) for k, v in merged_params.items()}
diff --git a/pydantic/config.py b/pydantic/config.py
index 1bb813d6eb2..728c499d29f 100644
--- a/pydantic/config.py
+++ b/pydantic/config.py
@@ -168,30 +168,41 @@ class Model(BaseModel):
     Whether an aliased field may be populated by its name as given by the model
     attribute, as well as the alias. Defaults to `False`.
 
-    Note:
-        The name of this configuration setting was changed in **v2.0** from
-        `allow_population_by_field_name` to `populate_by_name`.
+    !!! warning
+        `populate_by_name` usage is not recommended in v2.11+ and will be deprecated in v3.
+        Instead, you should use the [`validate_by_name`][pydantic.config.ConfigDict.validate_by_name] configuration setting.
 
-    ```python
-    from pydantic import BaseModel, ConfigDict, Field
+        When `validate_by_name=True` and `validate_by_alias=True`, this is strictly equivalent to the
+        previous behavior of `populate_by_name=True`.
 
-    class User(BaseModel):
-        model_config = ConfigDict(populate_by_name=True)
+        In v2.11, we also introduced a [`validate_by_alias`][pydantic.config.ConfigDict.validate_by_alias] setting that introduces more fine grained
+        control for validation behavior.
 
-        name: str = Field(alias='full_name')  # (1)!
-        age: int
+        Here's how you might go about using the new settings to achieve the same behavior:
 
-    user = User(full_name='John Doe', age=20)  # (2)!
-    print(user)
-    #> name='John Doe' age=20
-    user = User(name='John Doe', age=20)  # (3)!
-    print(user)
-    #> name='John Doe' age=20
-    ```
+        ```python
+        from datetime import datetime
+
+        from pydantic import BaseModel, ConfigDict, Field
+
+        class User(BaseModel):
+            model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
+
+            date_of_birth: datetime = Field(alias='DOB')  # (1)!
+            name: str
+
+        user = User(DOB='2022-01-01', name='John Doe')  # (2)!
+        print(user)
+        #> date_of_birth=datetime.datetime(2022, 1, 1, 0, 0) name='John Doe'
 
-    1. The field `'name'` has an alias `'full_name'`.
-    2. The model is populated by the alias `'full_name'`.
-    3. The model is populated by the field name `'name'`.
+        user = User(date_of_birth='2022-01-01', name='John Doe')  # (3)!
+        print(user)
+        #> date_of_birth=datetime.datetime(2022, 1, 1, 0, 0) name='John Doe'
+        ```
+
+        1. The field `'date_of_birth'` has an alias `'DOB'`.
+        2. The model is populated by the alias `'DOB'`.
+        3. The model is populated by the attribute name `'date_of_birth'`.
     """
 
     use_enum_values: bool
@@ -1025,6 +1036,111 @@ class Model(BaseModel):
         as the performance difference is minimal if repeated strings are rare.
     """
 
+    validate_by_alias: bool
+    """
+    Whether an aliased field may be populated by its alias. Defaults to `True`.
+
+    !!! note
+        In v2.11, `validate_by_alias` was introduced in conjunction with [`validate_by_name`][pydantic.ConfigDict.validate_by_name]
+        to empower users with more fine grained validation control. In <v2.11, disabling validation by alias was not possible.
+
+    !!! tip
+        If you set `validate_by_alias` to `False`, you should set `validate_by_name` to `True` to ensure that the field can still be populated.
+
+    Here's an example of disabling validation by alias:
+
+    ```py
+    from pydantic import BaseModel, ConfigDict, Field
+
+    class Model(BaseModel):
+        model_config = ConfigDict(validate_by_name=True, validate_by_alias=False)
+
+        my_field: str = Field(validation_alias='my_alias')  # (1)!
+
+    m = Model(my_field='foo')  # (2)!
+    print(m)
+    #> my_field='foo'
+    ```
+
+    1. The field `'my_field'` has an alias `'my_alias'`.
+    2. The model can only be populated by the attribute name `'my_field'`.
+
+    !!! warning
+        You cannot set both `validate_by_alias` and `validate_by_name` to `False`.
+        This would make it impossible to populate an attribute.
+
+        See [usage errors](../errors/usage_errors.md#validate-by-alias-and-name-false) for an example.
+    """
+
+    validate_by_name: bool
+    """
+    Whether an aliased field may be populated by its name as given by the model
+    attribute. Defaults to `False`.
+
+    !!! note
+        In v2.0-v2.10, the `populate_by_name` configuration setting was used to specify
+        whether or not a field could be populated by its name **and** alias.
+
+        In v2.11, `validate_by_name` was introduced in conjunction with [`validate_by_alias`][pydantic.ConfigDict.validate_by_alias]
+        to empower users with more fine grained validation behavior control.
+
+    ```python
+    from pydantic import BaseModel, ConfigDict, Field
+
+    class Model(BaseModel):
+        model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
+
+        my_field: str = Field(validation_alias='my_alias')  # (1)!
+
+    m = Model(my_alias='foo')  # (2)!
+    print(m)
+    #> my_field='foo'
+
+    m = Model(my_field='foo')  # (3)!
+    print(m)
+    #> my_field='foo'
+    ```
+
+    1. The field `'my_field'` has an alias `'my_alias'`.
+    2. The model is populated by the alias `'my_alias'`.
+    3. The model is populated by the attribute name `'my_field'`.
+
+    !!! warning
+        You cannot set both `validate_by_alias` and `validate_by_name` to `False`.
+        This would make it impossible to populate an attribute.
+
+        This also means you can't set `validate_by_alias` to `False` and leave `validate_by_name` unset,
+        as `validate_by_name` defaults to `False`.
+
+        See [usage errors](../errors/usage_errors.md#validate-by-alias-and-name-false) for an example.
+    """
+
+    serialize_by_alias: bool
+    """
+    Whether an aliased field should be serialized by its alias. Defaults to `False`.
+
+    Note: In v2.11, `serialize_by_alias` was introduced to address the
+    [popular request](https://github.com/pydantic/pydantic/issues/8379)
+    for consistency with alias behavior for validation and serialization settings.
+    In v3, the default value is expected to change to `True` for consistency with the validation default.
+
+    ```python
+    from pydantic import BaseModel, ConfigDict, Field
+
+    class Model(BaseModel):
+        model_config = ConfigDict(serialize_by_alias=True)
+
+        my_field: str = Field(serialization_alias='my_alias')  # (1)!
+
+    m = Model(my_field='foo')
+    print(m.model_dump())  # (2)!
+    #> {'my_alias': 'foo'}
+    ```
+
+    1. The field `'my_field'` has an alias `'my_alias'`.
+    2. The model is serialized using the alias `'my_alias'` for the `'my_field'` attribute.
+    """
+
 
 _TypeT = TypeVar('_TypeT', bound=type)
 
diff --git a/pydantic/errors.py b/pydantic/errors.py
index 024eb5d7fff..df2f364c2cf 100644
--- a/pydantic/errors.py
+++ b/pydantic/errors.py
@@ -72,6 +72,7 @@
     'unpack-typed-dict',
     'overlapping-unpack-typed-dict',
     'invalid-self-type',
+    'validate-by-alias-and-name-false',
 ]
 
 
diff --git a/pydantic/json_schema.py b/pydantic/json_schema.py
index 0c95fd4dc7e..ca031ea98a1 100644
--- a/pydantic/json_schema.py
+++ b/pydantic/json_schema.py
@@ -2158,15 +2158,15 @@ def encode_default(self, dft: Any) -> Any:
             default = (
                 dft
                 if _type_has_config(type(dft))
-                else TypeAdapter(type(dft), config=config.config_dict).dump_python(dft, mode='json')
+                else TypeAdapter(type(dft), config=config.config_dict).dump_python(
+                    dft, by_alias=self.by_alias, mode='json'
+                )
             )
         except PydanticSchemaGenerationError:
             raise pydantic_core.PydanticSerializationError(f'Unable to encode default value {dft}')
 
         return pydantic_core.to_jsonable_python(
-            default,
-            timedelta_mode=config.ser_json_timedelta,
-            bytes_mode=config.ser_json_bytes,
+            default, timedelta_mode=config.ser_json_timedelta, bytes_mode=config.ser_json_bytes, by_alias=self.by_alias
         )
 
     def update_with_validations(
diff --git a/pydantic/main.py b/pydantic/main.py
index 7ee883659cc..1ea01ba0154 100644
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -415,7 +415,7 @@ def model_dump(
         include: IncEx | None = None,
         exclude: IncEx | None = None,
         context: Any | None = None,
-        by_alias: bool = False,
+        by_alias: bool | None = None,
         exclude_unset: bool = False,
         exclude_defaults: bool = False,
         exclude_none: bool = False,
@@ -473,7 +473,7 @@ def model_dump_json(
         include: IncEx | None = None,
         exclude: IncEx | None = None,
         context: Any | None = None,
-        by_alias: bool = False,
+        by_alias: bool | None = None,
         exclude_unset: bool = False,
         exclude_defaults: bool = False,
         exclude_none: bool = False,
@@ -662,6 +662,8 @@ def model_validate(
         strict: bool | None = None,
         from_attributes: bool | None = None,
         context: Any | None = None,
+        by_alias: bool | None = None,
+        by_name: bool | None = None,
     ) -> Self:
         """Validate a pydantic model instance.
 
@@ -670,6 +672,8 @@ def model_validate(
             strict: Whether to enforce types strictly.
             from_attributes: Whether to extract data from object attributes.
             context: Additional context to pass to the validator.
+            by_alias: Whether to use the field's alias to match the input data to an attribute.
+            by_name: Whether to use the field's name to match the input data to an attribute.
 
         Raises:
             ValidationError: If the object could not be validated.
@@ -679,8 +683,15 @@ def model_validate(
         """
         # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks
         __tracebackhide__ = True
+
+        if by_alias is False and by_name is not True:
+            raise PydanticUserError(
+                'At least one of `by_alias` or `by_name` must be set to True.',
+                code='validate-by-alias-and-name-false',
+            )
+
         return cls.__pydantic_validator__.validate_python(
-            obj, strict=strict, from_attributes=from_attributes, context=context
+            obj, strict=strict, from_attributes=from_attributes, context=context, by_alias=by_alias, by_name=by_name
         )
 
     @classmethod
@@ -690,6 +701,8 @@ def model_validate_json(
         *,
         strict: bool | None = None,
         context: Any | None = None,
+        by_alias: bool | None = None,
+        by_name: bool | None = None,
     ) -> Self:
         """!!! abstract "Usage Documentation"
             [JSON Parsing](../concepts/json.md#json-parsing)
@@ -700,6 +713,8 @@ def model_validate_json(
             json_data: The JSON data to validate.
             strict: Whether to enforce types strictly.
             context: Extra variables to pass to the validator.
+            by_alias: Whether to use the field's alias to match the input data to an attribute.
+            by_name: Whether to use the field's name to match the input data to an attribute.
 
         Returns:
             The validated Pydantic model.
@@ -709,7 +724,16 @@ def model_validate_json(
         """
         # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks
         __tracebackhide__ = True
-        return cls.__pydantic_validator__.validate_json(json_data, strict=strict, context=context)
+
+        if by_alias is False and by_name is not True:
+            raise PydanticUserError(
+                'At least one of `by_alias` or `by_name` must be set to True.',
+                code='validate-by-alias-and-name-false',
+            )
+
+        return cls.__pydantic_validator__.validate_json(
+            json_data, strict=strict, context=context, by_alias=by_alias, by_name=by_name
+        )
 
     @classmethod
     def model_validate_strings(
@@ -718,6 +742,8 @@ def model_validate_strings(
         *,
         strict: bool | None = None,
         context: Any | None = None,
+        by_alias: bool | None = None,
+        by_name: bool | None = None,
     ) -> Self:
         """Validate the given object with string data against the Pydantic model.
 
@@ -725,13 +751,24 @@ def model_validate_strings(
             obj: The object containing string data to validate.
             strict: Whether to enforce types strictly.
             context: Extra variables to pass to the validator.
+            by_alias: Whether to use the field's alias to match the input data to an attribute.
+            by_name: Whether to use the field's name to match the input data to an attribute.
 
         Returns:
             The validated Pydantic model.
         """
         # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks
         __tracebackhide__ = True
-        return cls.__pydantic_validator__.validate_strings(obj, strict=strict, context=context)
+
+        if by_alias is False and by_name is not True:
+            raise PydanticUserError(
+                'At least one of `by_alias` or `by_name` must be set to True.',
+                code='validate-by-alias-and-name-false',
+            )
+
+        return cls.__pydantic_validator__.validate_strings(
+            obj, strict=strict, context=context, by_alias=by_alias, by_name=by_name
+        )
 
     @classmethod
     def __get_pydantic_core_schema__(cls, source: type[BaseModel], handler: GetCoreSchemaHandler, /) -> CoreSchema:
diff --git a/pydantic/mypy.py b/pydantic/mypy.py
index 3ebf02baf80..de7edaa0b6c 100644
--- a/pydantic/mypy.py
+++ b/pydantic/mypy.py
@@ -422,7 +422,8 @@ class PydanticModelTransformer:
         'extra',
         'frozen',
         'from_attributes',
-        'populate_by_name',
+        'validate_by_alias',
+        'validate_by_name',
         'alias_generator',
         'strict',
     }
@@ -564,7 +565,7 @@ def collect_config(self) -> ModelConfigData:  # noqa: C901 (ignore complexity)
             if (
                 stmt
                 and config.has_alias_generator
-                and not config.populate_by_name
+                and not config.validate_by_name
                 and self.plugin_config.warn_required_dynamic_aliases
             ):
                 error_required_dynamic_aliases(self._api, stmt)
@@ -776,7 +777,7 @@ def collect_field_or_class_var_from_stmt(  # noqa C901
             return PydanticModelClassVar(lhs.name)
 
         alias, has_dynamic_alias = self.get_alias_info(stmt)
-        if has_dynamic_alias and not model_config.populate_by_name and self.plugin_config.warn_required_dynamic_aliases:
+        if has_dynamic_alias and not model_config.validate_by_name and self.plugin_config.warn_required_dynamic_aliases:
             error_required_dynamic_aliases(self._api, stmt)
         is_frozen = self.is_field_frozen(stmt)
 
@@ -844,8 +845,8 @@ def add_initializer(
 
         typed = self.plugin_config.init_typed
         model_strict = bool(config.strict)
-        use_alias = config.populate_by_name is not True
-        requires_dynamic_aliases = bool(config.has_alias_generator and not config.populate_by_name)
+        use_alias = config.validate_by_name is not True
+        requires_dynamic_aliases = bool(config.has_alias_generator and not config.validate_by_name)
         args = self.get_field_arguments(
             fields,
             typed=typed,
@@ -1116,7 +1117,7 @@ def should_init_forbid_extra(self, fields: list[PydanticModelField], config: Mod
         We disallow arbitrary kwargs if the extra config setting is "forbid", or if the plugin config says to,
         *unless* a required dynamic alias is present (since then we can't determine a valid signature).
         """
-        if not config.populate_by_name:
+        if not config.validate_by_name:
             if self.is_dynamic_alias_present(fields, bool(config.has_alias_generator)):
                 return False
         if config.forbid_extra:
@@ -1146,14 +1147,16 @@ def __init__(
         forbid_extra: bool | None = None,
         frozen: bool | None = None,
         from_attributes: bool | None = None,
-        populate_by_name: bool | None = None,
+        validate_by_alias: bool | None = None,
+        validate_by_name: bool | None = None,
         has_alias_generator: bool | None = None,
         strict: bool | None = None,
     ):
         self.forbid_extra = forbid_extra
         self.frozen = frozen
         self.from_attributes = from_attributes
-        self.populate_by_name = populate_by_name
+        self.validate_by_alias = validate_by_alias
+        self.validate_by_name = validate_by_name
         self.has_alias_generator = has_alias_generator
         self.strict = strict
 
diff --git a/pydantic/plugin/__init__.py b/pydantic/plugin/__init__.py
index 1fdfb3b63d2..362030584fc 100644
--- a/pydantic/plugin/__init__.py
+++ b/pydantic/plugin/__init__.py
@@ -116,6 +116,8 @@ def on_enter(
         from_attributes: bool | None = None,
         context: dict[str, Any] | None = None,
         self_instance: Any | None = None,
+        by_alias: bool | None = None,
+        by_name: bool | None = None,
     ) -> None:
         """Callback to be notified of validation start, and create an instance of the event handler.
 
@@ -126,6 +128,8 @@ def on_enter(
             context: The context to use for validation, this is passed to functional validators.
             self_instance: An instance of a model to set attributes on from validation, this is used when running
                 validation from the `__init__` method of a model.
+            by_alias: Whether to use the field's alias to match the input data to an attribute.
+            by_name: Whether to use the field's name to match the input data to an attribute.
         """
         pass
 
@@ -140,6 +144,8 @@ def on_enter(
         strict: bool | None = None,
         context: dict[str, Any] | None = None,
         self_instance: Any | None = None,
+        by_alias: bool | None = None,
+        by_name: bool | None = None,
     ) -> None:
         """Callback to be notified of validation start, and create an instance of the event handler.
 
@@ -149,6 +155,8 @@ def on_enter(
             context: The context to use for validation, this is passed to functional validators.
             self_instance: An instance of a model to set attributes on from validation, this is used when running
                 validation from the `__init__` method of a model.
+            by_alias: Whether to use the field's alias to match the input data to an attribute.
+            by_name: Whether to use the field's name to match the input data to an attribute.
         """
         pass
 
@@ -160,7 +168,13 @@ class ValidateStringsHandlerProtocol(BaseValidateHandlerProtocol, Protocol):
     """Event handler for `SchemaValidator.validate_strings`."""
 
     def on_enter(
-        self, input: StringInput, *, strict: bool | None = None, context: dict[str, Any] | None = None
+        self,
+        input: StringInput,
+        *,
+        strict: bool | None = None,
+        context: dict[str, Any] | None = None,
+        by_alias: bool | None = None,
+        by_name: bool | None = None,
     ) -> None:
         """Callback to be notified of validation start, and create an instance of the event handler.
 
@@ -168,5 +182,7 @@ def on_enter(
             input: The string data to be validated.
             strict: Whether to validate the object in strict mode.
             context: The context to use for validation, this is passed to functional validators.
+            by_alias: Whether to use the field's alias to match the input data to an attribute.
+            by_name: Whether to use the field's name to match the input data to an attribute.
         """
         pass
diff --git a/pydantic/root_model.py b/pydantic/root_model.py
index 0c9a95568d5..8b3ff014edc 100644
--- a/pydantic/root_model.py
+++ b/pydantic/root_model.py
@@ -125,7 +125,7 @@ def model_dump(  # type: ignore
             include: Any = None,
             exclude: Any = None,
             context: dict[str, Any] | None = None,
-            by_alias: bool = False,
+            by_alias: bool | None = None,
             exclude_unset: bool = False,
             exclude_defaults: bool = False,
             exclude_none: bool = False,
diff --git a/pydantic/type_adapter.py b/pydantic/type_adapter.py
index 0187dc20739..24d89f58a8a 100644
--- a/pydantic/type_adapter.py
+++ b/pydantic/type_adapter.py
@@ -387,6 +387,8 @@ def validate_python(
         from_attributes: bool | None = None,
         context: dict[str, Any] | None = None,
         experimental_allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False,
+        by_alias: bool | None = None,
+        by_name: bool | None = None,
     ) -> T:
         """Validate a Python object against the model.
 
@@ -400,6 +402,8 @@ def validate_python(
                 * False / 'off': Default behavior, no partial validation.
                 * True / 'on': Enable partial validation.
                 * 'trailing-strings': Enable partial validation and allow trailing strings in the input.
+            by_alias: Whether to use the field's alias to match the input data to an attribute.
+            by_name: Whether to use the field's name to match the input data to an attribute.
 
         !!! note
             When using `TypeAdapter` with a Pydantic `dataclass`, the use of the `from_attributes`
@@ -408,12 +412,20 @@ def validate_python(
         Returns:
             The validated object.
         """
+        if by_alias is False and by_name is not True:
+            raise PydanticUserError(
+                'At least one of `by_alias` or `by_name` must be set to True.',
+                code='validate-by-alias-and-name-false',
+            )
+
         return self.validator.validate_python(
             object,
             strict=strict,
             from_attributes=from_attributes,
             context=context,
             allow_partial=experimental_allow_partial,
+            by_alias=by_alias,
+            by_name=by_name,
         )
 
     def validate_json(
@@ -424,6 +436,8 @@ def validate_json(
         strict: bool | None = None,
         context: dict[str, Any] | None = None,
         experimental_allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False,
+        by_alias: bool | None = None,
+        by_name: bool | None = None,
     ) -> T:
         """!!! abstract "Usage Documentation"
             [JSON Parsing](../concepts/json.md#json-parsing)
@@ -439,12 +453,25 @@ def validate_json(
                 * False / 'off': Default behavior, no partial validation.
                 * True / 'on': Enable partial validation.
                 * 'trailing-strings': Enable partial validation and allow trailing strings in the input.
+            by_alias: Whether to use the field's alias to match the input data to an attribute.
+            by_name: Whether to use the field's name to match the input data to an attribute.
 
         Returns:
             The validated object.
         """
+        if by_alias is False and by_name is not True:
+            raise PydanticUserError(
+                'At least one of `by_alias` or `by_name` must be set to True.',
+                code='validate-by-alias-and-name-false',
+            )
+
         return self.validator.validate_json(
-            data, strict=strict, context=context, allow_partial=experimental_allow_partial
+            data,
+            strict=strict,
+            context=context,
+            allow_partial=experimental_allow_partial,
+            by_alias=by_alias,
+            by_name=by_name,
         )
 
     def validate_strings(
@@ -455,6 +482,8 @@ def validate_strings(
         strict: bool | None = None,
         context: dict[str, Any] | None = None,
         experimental_allow_partial: bool | Literal['off', 'on', 'trailing-strings'] = False,
+        by_alias: bool | None = None,
+        by_name: bool | None = None,
     ) -> T:
         """Validate object contains string data against the model.
 
@@ -467,12 +496,25 @@ def validate_strings(
                 * False / 'off': Default behavior, no partial validation.
                 * True / 'on': Enable partial validation.
                 * 'trailing-strings': Enable partial validation and allow trailing strings in the input.
+            by_alias: Whether to use the field's alias to match the input data to an attribute.
+            by_name: Whether to use the field's name to match the input data to an attribute.
 
         Returns:
             The validated object.
         """
+        if by_alias is False and by_name is not True:
+            raise PydanticUserError(
+                'At least one of `by_alias` or `by_name` must be set to True.',
+                code='validate-by-alias-and-name-false',
+            )
+
         return self.validator.validate_strings(
-            obj, strict=strict, context=context, allow_partial=experimental_allow_partial
+            obj,
+            strict=strict,
+            context=context,
+            allow_partial=experimental_allow_partial,
+            by_alias=by_alias,
+            by_name=by_name,
         )
 
     def get_default_value(self, *, strict: bool | None = None, context: dict[str, Any] | None = None) -> Some[T] | None:
@@ -495,7 +537,7 @@ def dump_python(
         mode: Literal['json', 'python'] = 'python',
         include: IncEx | None = None,
         exclude: IncEx | None = None,
-        by_alias: bool = False,
+        by_alias: bool | None = None,
         exclude_unset: bool = False,
         exclude_defaults: bool = False,
         exclude_none: bool = False,
@@ -551,7 +593,7 @@ def dump_json(
         indent: int | None = None,
         include: IncEx | None = None,
         exclude: IncEx | None = None,
-        by_alias: bool = False,
+        by_alias: bool | None = None,
         exclude_unset: bool = False,
         exclude_defaults: bool = False,
         exclude_none: bool = False,
diff --git a/pydantic/types.py b/pydantic/types.py
index 648416af803..02e61dde21b 100644
--- a/pydantic/types.py
+++ b/pydantic/types.py
@@ -3055,7 +3055,7 @@ class ThanksgivingDinner(BaseModel):
     A `str` discriminator must be the name of a field to discriminate against.
     """
     custom_error_type: str | None = None
-    """Type to use in [custom errors](../errors/errors.md#custom-errors) replacing the standard discriminated union
+    """Type to use in [custom errors](../errors/errors.md) replacing the standard discriminated union
     validation errors.
     """
     custom_error_message: str | None = None
diff --git a/pydantic/version.py b/pydantic/version.py
index 7ffb17581e9..761c1e82050 100644
--- a/pydantic/version.py
+++ b/pydantic/version.py
@@ -68,7 +68,7 @@ def version_info() -> str:
 def check_pydantic_core_version() -> bool:
     """Check that the installed `pydantic-core` dependency is compatible."""
     # Keep this in sync with the version constraint in the `pyproject.toml` dependencies:
-    return __pydantic_core_version__ == '2.29.0'
+    return __pydantic_core_version__ == '2.30.0'
 
 
 def parse_mypy_version(version: str) -> tuple[int, int, int]:
diff --git a/tests/mypy/modules/metaclass_args.py b/tests/mypy/modules/metaclass_args.py
index 8607a85f259..5a7394e0e17 100644
--- a/tests/mypy/modules/metaclass_args.py
+++ b/tests/mypy/modules/metaclass_args.py
@@ -5,20 +5,20 @@ class ConfigClassUsed(BaseModel):
     i: int = Field(2, alias='j')
 
     class Config:
-        populate_by_name = True
+        validate_by_name = True
 
 
 ConfigClassUsed(i=None)
 
 
-class MetaclassArgumentsNoDefault(BaseModel, populate_by_name=True):
+class MetaclassArgumentsNoDefault(BaseModel, validate_by_name=True):
     i: int = Field(alias='j')
 
 
 MetaclassArgumentsNoDefault(i=None)
 
 
-class MetaclassArgumentsWithDefault(BaseModel, populate_by_name=True):
+class MetaclassArgumentsWithDefault(BaseModel, validate_by_name=True):
     i: int = Field(2, alias='j')
 
 
diff --git a/tests/mypy/modules/plugin_fail.py b/tests/mypy/modules/plugin_fail.py
index aefa4838e9d..40ffb1ddccd 100644
--- a/tests/mypy/modules/plugin_fail.py
+++ b/tests/mypy/modules/plugin_fail.py
@@ -153,14 +153,14 @@ class DynamicAliasModel2(BaseModel):
     x: str = Field(..., alias=x_alias)
     z: int
 
-    model_config = ConfigDict(populate_by_name=True)
+    model_config = ConfigDict(validate_by_name=True)
 
 
 DynamicAliasModel2(y='y', z=1)
 DynamicAliasModel2(x='y', z=1)
 
 
-class KwargsDynamicAliasModel(BaseModel, populate_by_name=True):
+class KwargsDynamicAliasModel(BaseModel, validate_by_name=True):
     x: str = Field(..., alias=x_alias)
     z: int
 
diff --git a/tests/mypy/modules/plugin_fail_baseConfig.py b/tests/mypy/modules/plugin_fail_baseConfig.py
index ee433deeebd..da2b6588688 100644
--- a/tests/mypy/modules/plugin_fail_baseConfig.py
+++ b/tests/mypy/modules/plugin_fail_baseConfig.py
@@ -167,14 +167,14 @@ class DynamicAliasModel2(BaseModel):
     z: int
 
     class Config:
-        populate_by_name = True
+        validate_by_name = True
 
 
 DynamicAliasModel2(y='y', z=1)
 DynamicAliasModel2(x='y', z=1)
 
 
-class KwargsDynamicAliasModel(BaseModel, populate_by_name=True):
+class KwargsDynamicAliasModel(BaseModel, validate_by_name=True):
     x: str = Field(..., alias=x_alias)
     z: int
 
diff --git a/tests/mypy/outputs/1.10.1/mypy-default_ini/metaclass_args.py b/tests/mypy/outputs/1.10.1/mypy-default_ini/metaclass_args.py
index c8bd9e91a57..a1ac9dd1252 100644
--- a/tests/mypy/outputs/1.10.1/mypy-default_ini/metaclass_args.py
+++ b/tests/mypy/outputs/1.10.1/mypy-default_ini/metaclass_args.py
@@ -5,14 +5,14 @@ class ConfigClassUsed(BaseModel):
     i: int = Field(2, alias='j')
 
     class Config:
-        populate_by_name = True
+        validate_by_name = True
 
 
 ConfigClassUsed(i=None)
 # MYPY: error: Unexpected keyword argument "i" for "ConfigClassUsed"  [call-arg]
 
 
-class MetaclassArgumentsNoDefault(BaseModel, populate_by_name=True):
+class MetaclassArgumentsNoDefault(BaseModel, validate_by_name=True):
     i: int = Field(alias='j')
 
 
@@ -20,7 +20,7 @@ class MetaclassArgumentsNoDefault(BaseModel, populate_by_name=True):
 # MYPY: error: Unexpected keyword argument "i" for "MetaclassArgumentsNoDefault"  [call-arg]
 
 
-class MetaclassArgumentsWithDefault(BaseModel, populate_by_name=True):
+class MetaclassArgumentsWithDefault(BaseModel, validate_by_name=True):
     i: int = Field(2, alias='j')
 
 
diff --git a/tests/mypy/outputs/1.10.1/mypy-plugin-strict_ini/plugin_fail.py b/tests/mypy/outputs/1.10.1/mypy-plugin-strict_ini/plugin_fail.py
index 98c9225688d..f0311cb23b5 100644
--- a/tests/mypy/outputs/1.10.1/mypy-plugin-strict_ini/plugin_fail.py
+++ b/tests/mypy/outputs/1.10.1/mypy-plugin-strict_ini/plugin_fail.py
@@ -189,7 +189,7 @@ class DynamicAliasModel2(BaseModel):
     x: str = Field(..., alias=x_alias)
     z: int
 
-    model_config = ConfigDict(populate_by_name=True)
+    model_config = ConfigDict(validate_by_name=True)
 
 
 DynamicAliasModel2(y='y', z=1)
@@ -197,7 +197,7 @@ class DynamicAliasModel2(BaseModel):
 DynamicAliasModel2(x='y', z=1)
 
 
-class KwargsDynamicAliasModel(BaseModel, populate_by_name=True):
+class KwargsDynamicAliasModel(BaseModel, validate_by_name=True):
     x: str = Field(..., alias=x_alias)
     z: int
 
diff --git a/tests/mypy/outputs/1.10.1/mypy-plugin-strict_ini/plugin_fail_baseConfig.py b/tests/mypy/outputs/1.10.1/mypy-plugin-strict_ini/plugin_fail_baseConfig.py
index b9f90bb177c..7fa0af3289c 100644
--- a/tests/mypy/outputs/1.10.1/mypy-plugin-strict_ini/plugin_fail_baseConfig.py
+++ b/tests/mypy/outputs/1.10.1/mypy-plugin-strict_ini/plugin_fail_baseConfig.py
@@ -202,7 +202,7 @@ class DynamicAliasModel2(BaseModel):
     z: int
 
     class Config:
-        populate_by_name = True
+        validate_by_name = True
 
 
 DynamicAliasModel2(y='y', z=1)
@@ -210,7 +210,7 @@ class Config:
 DynamicAliasModel2(x='y', z=1)
 
 
-class KwargsDynamicAliasModel(BaseModel, populate_by_name=True):
+class KwargsDynamicAliasModel(BaseModel, validate_by_name=True):
     x: str = Field(..., alias=x_alias)
     z: int
 
diff --git a/tests/mypy/outputs/1.10.1/mypy-plugin-very-strict_ini/metaclass_args.py b/tests/mypy/outputs/1.10.1/mypy-plugin-very-strict_ini/metaclass_args.py
index 7cacff9e86d..f58945b67c9 100644
--- a/tests/mypy/outputs/1.10.1/mypy-plugin-very-strict_ini/metaclass_args.py
+++ b/tests/mypy/outputs/1.10.1/mypy-plugin-very-strict_ini/metaclass_args.py
@@ -5,14 +5,14 @@ class ConfigClassUsed(BaseModel):
     i: int = Field(2, alias='j')
 
     class Config:
-        populate_by_name = True
+        validate_by_name = True
 
 
 ConfigClassUsed(i=None)
 # MYPY: error: Argument "i" to "ConfigClassUsed" has incompatible type "None"; expected "int"  [arg-type]
 
 
-class MetaclassArgumentsNoDefault(BaseModel, populate_by_name=True):
+class MetaclassArgumentsNoDefault(BaseModel, validate_by_name=True):
     i: int = Field(alias='j')
 
 
@@ -20,7 +20,7 @@ class MetaclassArgumentsNoDefault(BaseModel, populate_by_name=True):
 # MYPY: error: Argument "i" to "MetaclassArgumentsNoDefault" has incompatible type "None"; expected "int"  [arg-type]
 
 
-class MetaclassArgumentsWithDefault(BaseModel, populate_by_name=True):
+class MetaclassArgumentsWithDefault(BaseModel, validate_by_name=True):
     i: int = Field(2, alias='j')
 
 
diff --git a/tests/mypy/outputs/1.10.1/mypy-plugin_ini/plugin_fail.py b/tests/mypy/outputs/1.10.1/mypy-plugin_ini/plugin_fail.py
index ab55823c943..4bcd940b945 100644
--- a/tests/mypy/outputs/1.10.1/mypy-plugin_ini/plugin_fail.py
+++ b/tests/mypy/outputs/1.10.1/mypy-plugin_ini/plugin_fail.py
@@ -183,7 +183,7 @@ class DynamicAliasModel2(BaseModel):
     x: str = Field(..., alias=x_alias)
     z: int
 
-    model_config = ConfigDict(populate_by_name=True)
+    model_config = ConfigDict(validate_by_name=True)
 
 
 DynamicAliasModel2(y='y', z=1)
@@ -191,7 +191,7 @@ class DynamicAliasModel2(BaseModel):
 DynamicAliasModel2(x='y', z=1)
 
 
-class KwargsDynamicAliasModel(BaseModel, populate_by_name=True):
+class KwargsDynamicAliasModel(BaseModel, validate_by_name=True):
     x: str = Field(..., alias=x_alias)
     z: int
 
diff --git a/tests/mypy/outputs/1.10.1/mypy-plugin_ini/plugin_fail_baseConfig.py b/tests/mypy/outputs/1.10.1/mypy-plugin_ini/plugin_fail_baseConfig.py
index fdc24df2f30..e25047d697c 100644
--- a/tests/mypy/outputs/1.10.1/mypy-plugin_ini/plugin_fail_baseConfig.py
+++ b/tests/mypy/outputs/1.10.1/mypy-plugin_ini/plugin_fail_baseConfig.py
@@ -196,7 +196,7 @@ class DynamicAliasModel2(BaseModel):
     z: int
 
     class Config:
-        populate_by_name = True
+        validate_by_name = True
 
 
 DynamicAliasModel2(y='y', z=1)
@@ -204,7 +204,7 @@ class Config:
 DynamicAliasModel2(x='y', z=1)
 
 
-class KwargsDynamicAliasModel(BaseModel, populate_by_name=True):
+class KwargsDynamicAliasModel(BaseModel, validate_by_name=True):
     x: str = Field(..., alias=x_alias)
     z: int
 
diff --git a/tests/mypy/outputs/1.10.1/pyproject-plugin-strict_toml/plugin_fail.py b/tests/mypy/outputs/1.10.1/pyproject-plugin-strict_toml/plugin_fail.py
index 98c9225688d..f0311cb23b5 100644
--- a/tests/mypy/outputs/1.10.1/pyproject-plugin-strict_toml/plugin_fail.py
+++ b/tests/mypy/outputs/1.10.1/pyproject-plugin-strict_toml/plugin_fail.py
@@ -189,7 +189,7 @@ class DynamicAliasModel2(BaseModel):
     x: str = Field(..., alias=x_alias)
     z: int
 
-    model_config = ConfigDict(populate_by_name=True)
+    model_config = ConfigDict(validate_by_name=True)
 
 
 DynamicAliasModel2(y='y', z=1)
@@ -197,7 +197,7 @@ class DynamicAliasModel2(BaseModel):
 DynamicAliasModel2(x='y', z=1)
 
 
-class KwargsDynamicAliasModel(BaseModel, populate_by_name=True):
+class KwargsDynamicAliasModel(BaseModel, validate_by_name=True):
     x: str = Field(..., alias=x_alias)
     z: int
 
diff --git a/tests/mypy/outputs/1.10.1/pyproject-plugin-strict_toml/plugin_fail_baseConfig.py b/tests/mypy/outputs/1.10.1/pyproject-plugin-strict_toml/plugin_fail_baseConfig.py
index b9f90bb177c..7fa0af3289c 100644
--- a/tests/mypy/outputs/1.10.1/pyproject-plugin-strict_toml/plugin_fail_baseConfig.py
+++ b/tests/mypy/outputs/1.10.1/pyproject-plugin-strict_toml/plugin_fail_baseConfig.py
@@ -202,7 +202,7 @@ class DynamicAliasModel2(BaseModel):
     z: int
 
     class Config:
-        populate_by_name = True
+        validate_by_name = True
 
 
 DynamicAliasModel2(y='y', z=1)
@@ -210,7 +210,7 @@ class Config:
 DynamicAliasModel2(x='y', z=1)
 
 
-class KwargsDynamicAliasModel(BaseModel, populate_by_name=True):
+class KwargsDynamicAliasModel(BaseModel, validate_by_name=True):
     x: str = Field(..., alias=x_alias)
     z: int
 
diff --git a/tests/mypy/outputs/1.10.1/pyproject-plugin_toml/plugin_fail.py b/tests/mypy/outputs/1.10.1/pyproject-plugin_toml/plugin_fail.py
index ab55823c943..4bcd940b945 100644
--- a/tests/mypy/outputs/1.10.1/pyproject-plugin_toml/plugin_fail.py
+++ b/tests/mypy/outputs/1.10.1/pyproject-plugin_toml/plugin_fail.py
@@ -183,7 +183,7 @@ class DynamicAliasModel2(BaseModel):
     x: str = Field(..., alias=x_alias)
     z: int
 
-    model_config = ConfigDict(populate_by_name=True)
+    model_config = ConfigDict(validate_by_name=True)
 
 
 DynamicAliasModel2(y='y', z=1)
@@ -191,7 +191,7 @@ class DynamicAliasModel2(BaseModel):
 DynamicAliasModel2(x='y', z=1)
 
 
-class KwargsDynamicAliasModel(BaseModel, populate_by_name=True):
+class KwargsDynamicAliasModel(BaseModel, validate_by_name=True):
     x: str = Field(..., alias=x_alias)
     z: int
 
diff --git a/tests/mypy/outputs/1.10.1/pyproject-plugin_toml/plugin_fail_baseConfig.py b/tests/mypy/outputs/1.10.1/pyproject-plugin_toml/plugin_fail_baseConfig.py
index fdc24df2f30..e25047d697c 100644
--- a/tests/mypy/outputs/1.10.1/pyproject-plugin_toml/plugin_fail_baseConfig.py
+++ b/tests/mypy/outputs/1.10.1/pyproject-plugin_toml/plugin_fail_baseConfig.py
@@ -196,7 +196,7 @@ class DynamicAliasModel2(BaseModel):
     z: int
 
     class Config:
-        populate_by_name = True
+        validate_by_name = True
 
 
 DynamicAliasModel2(y='y', z=1)
@@ -204,7 +204,7 @@ class Config:
 DynamicAliasModel2(x='y', z=1)
 
 
-class KwargsDynamicAliasModel(BaseModel, populate_by_name=True):
+class KwargsDynamicAliasModel(BaseModel, validate_by_name=True):
     x: str = Field(..., alias=x_alias)
     z: int
 
diff --git a/tests/test_aliases.py b/tests/test_aliases.py
index 493103c0cd9..ad076629069 100644
--- a/tests/test_aliases.py
+++ b/tests/test_aliases.py
@@ -1,7 +1,7 @@
 from contextlib import AbstractContextManager
 from contextlib import nullcontext as does_not_raise
 from inspect import signature
-from typing import Any, Optional
+from typing import Any, Optional, Union
 
 import pytest
 from dirty_equals import IsStr
@@ -14,6 +14,7 @@
     BaseModel,
     ConfigDict,
     Field,
+    PydanticUserError,
     ValidationError,
     computed_field,
 )
@@ -119,7 +120,7 @@ class Model(BaseModel):
 
 def test_pop_by_field_name():
     class Model(BaseModel):
-        model_config = ConfigDict(extra='forbid', populate_by_name=True)
+        model_config = ConfigDict(extra='forbid', validate_by_name=True)
         last_updated_by: Optional[str] = Field(None, alias='lastUpdatedBy')
 
     assert Model(lastUpdatedBy='foo').model_dump() == {'last_updated_by': 'foo'}
@@ -183,7 +184,7 @@ class Child(Parent):
 
 def test_alias_generator_parent():
     class Parent(BaseModel):
-        model_config = ConfigDict(populate_by_name=True, alias_generator=lambda f_name: f_name + '1')
+        model_config = ConfigDict(validate_by_name=True, alias_generator=lambda f_name: f_name + '1')
         x: int
 
     class Child(Parent):
@@ -382,7 +383,7 @@ class Model(BaseModel):
 
 
 @pytest.mark.parametrize(
-    'use_construct, populate_by_name_config, arg_name, expectation',
+    'use_construct, validate_by_name_config, arg_name, expectation',
     [
         [False, True, 'bar', does_not_raise()],
         [False, True, 'bar_', does_not_raise()],
@@ -394,16 +395,16 @@ class Model(BaseModel):
         [True, False, 'bar_', does_not_raise()],
     ],
 )
-def test_populate_by_name_config(
+def test_validate_by_name_config(
     use_construct: bool,
-    populate_by_name_config: bool,
+    validate_by_name_config: bool,
     arg_name: str,
     expectation: AbstractContextManager,
 ):
     expected_value: int = 7
 
     class Foo(BaseModel):
-        model_config = ConfigDict(populate_by_name=populate_by_name_config)
+        model_config = ConfigDict(validate_by_name=validate_by_name_config)
         bar_: int = Field(alias='bar')
 
     with expectation:
@@ -662,7 +663,7 @@ class Model(BaseModel):
 @pytest.mark.parametrize('alias_generator', upper_alias_generator)
 def test_alias_generator_with_computed_field(alias_generator) -> None:
     class Rectangle(BaseModel):
-        model_config = ConfigDict(populate_by_name=True, alias_generator=alias_generator)
+        model_config = ConfigDict(validate_by_name=True, alias_generator=alias_generator)
 
         width: int
         height: int
@@ -765,3 +766,89 @@ def b(self) -> str:
     assert Model.model_fields['a'].serialization_alias == ''
     assert Model.model_fields['a'].alias == 'a_alias'
     assert Model.model_computed_fields['b'].alias == ''
+
+
+@pytest.mark.parametrize('config_by_alias', [None, True, False])
+@pytest.mark.parametrize('config_by_name', [None, True, False])
+@pytest.mark.parametrize('runtime_by_alias', [None, True, False])
+@pytest.mark.parametrize('runtime_by_name', [None, True, False])
+def test_validation_alias_settings(
+    config_by_alias: Union[bool, None],
+    config_by_name: Union[bool, None],
+    runtime_by_alias: Union[bool, None],
+    runtime_by_name: Union[bool, None],
+) -> None:
+    """This test reflects the priority that applies for config vs runtime validation alias configuration.
+
+    Runtime values take precedence over config values, when set.
+    By default, `by_alias` is True and `by_name` is False.
+    """
+
+    if (config_by_alias is False and config_by_name is not True) or (
+        runtime_by_alias is False and runtime_by_name is not True
+    ):
+        pytest.skip("Can't have both validate_by_alias and validate_by_name as effectively False")
+
+    config_dict = {
+        **({'validate_by_alias': config_by_alias} if config_by_alias is not None else {}),
+        **({'validate_by_name': config_by_name} if config_by_name is not None else {}),
+    }
+
+    class Model(BaseModel):
+        model_config = ConfigDict(**config_dict)
+
+        a: int = Field(validation_alias='A')
+
+    alias_allowed = next(x for x in (runtime_by_alias, config_by_alias, True) if x is not None)
+    name_allowed = next(x for x in (runtime_by_name, config_by_name, False) if x is not None)
+
+    if alias_allowed:
+        assert Model.model_validate({'A': 1}, by_alias=runtime_by_alias, by_name=runtime_by_name).a == 1
+    if name_allowed:
+        assert Model.model_validate({'a': 1}, by_alias=runtime_by_alias, by_name=runtime_by_name).a == 1
+
+
+def test_user_error_on_validation_methods() -> None:
+    class Model(BaseModel):
+        a: int = Field(alias='A')
+
+    with pytest.raises(PydanticUserError, match='At least one of `by_alias` or `by_name` must be set to True.'):
+        Model.model_validate({'A': 1}, by_alias=False, by_name=False)
+
+    with pytest.raises(PydanticUserError, match='At least one of `by_alias` or `by_name` must be set to True.'):
+        Model.model_validate_json("{'A': 1}", by_alias=False, by_name=False)
+
+    with pytest.raises(PydanticUserError, match='At least one of `by_alias` or `by_name` must be set to True.'):
+        Model.model_validate_strings("{'A': 1}", by_alias=False, by_name=False)
+
+
+@pytest.mark.parametrize(
+    'config,runtime,expected',
+    [
+        (True, True, {'A': 1}),
+        (True, False, {'a': 1}),
+        (True, None, {'A': 1}),
+        (False, True, {'A': 1}),
+        (False, False, {'a': 1}),
+        (False, None, {'a': 1}),
+        (None, True, {'A': 1}),
+        (None, False, {'a': 1}),
+        (None, None, {'a': 1}),
+    ],
+)
+def test_serialization_alias_settings(
+    config: Union[bool, None], runtime: Union[bool, None], expected: dict[str, int]
+) -> None:
+    """This test reflects the priority that applies for config vs runtime serialization alias configuration.
+
+    Runtime value (by_alias) takes precedence over config value (serialize_by_alias).
+    If neither are set, the default, False, is used.
+    """
+
+    class Model(BaseModel):
+        model_config = ConfigDict(serialize_by_alias=config)
+
+        a: int = Field(serialization_alias='A')
+
+    model = Model(a=1)
+    assert model.model_dump(by_alias=runtime) == expected
diff --git a/tests/test_computed_fields.py b/tests/test_computed_fields.py
index e1c20db0c5d..b1a1bb698b5 100644
--- a/tests/test_computed_fields.py
+++ b/tests/test_computed_fields.py
@@ -612,7 +612,7 @@ def my_aliased_computed_field_2(self) -> int:
             return self.my_standard_field + 4
 
     class MySubModel(MyModel):
-        model_config = dict(alias_generator=to_camel, populate_by_name=True)
+        model_config = dict(alias_generator=to_camel, validate_by_name=True)
 
     model = MyModel(my_standard_field=1)
     assert model.model_dump() == {
diff --git a/tests/test_config.py b/tests/test_config.py
index f153de23638..bddd321dfce 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -142,7 +142,7 @@ def test_base_config_use_field_name(self):
         class Foo(BaseModel):
             foo: str = Field(alias='this is invalid')
 
-            model_config = ConfigDict(populate_by_name=True)
+            model_config = ConfigDict(validate_by_name=True)
 
         assert _equals(str(signature(Foo)), '(*, foo: str) -> None')
 
@@ -150,7 +150,7 @@ def test_base_config_does_not_use_reserved_word(self):
         class Foo(BaseModel):
             from_: str = Field(alias='from')
 
-            model_config = ConfigDict(populate_by_name=True)
+            model_config = ConfigDict(validate_by_name=True)
 
         assert _equals(str(signature(Foo)), '(*, from_: str) -> None')
 
@@ -300,7 +300,7 @@ class ModelNoArbitraryTypes(BaseModel):
                 x: Tup
 
     @pytest.mark.parametrize(
-        'use_construct, populate_by_name_config, arg_name, expectation',
+        'use_construct, validate_by_name_config, arg_name, expectation',
         [
             [False, True, 'bar', does_not_raise()],
             [False, True, 'bar_', does_not_raise()],
@@ -312,10 +312,10 @@ class ModelNoArbitraryTypes(BaseModel):
             [True, False, 'bar_', does_not_raise()],
         ],
     )
-    def test_populate_by_name_config(
+    def test_validate_by_name_config(
         self,
         use_construct: bool,
-        populate_by_name_config: bool,
+        validate_by_name_config: bool,
         arg_name: str,
         expectation: AbstractContextManager,
     ):
@@ -324,7 +324,7 @@ def test_populate_by_name_config(
         class Foo(BaseModel):
             bar_: int = Field(alias='bar')
 
-            model_config = dict(populate_by_name=populate_by_name_config)
+            model_config = dict(validate_by_name=validate_by_name_config)
 
         with expectation:
             if use_construct:
@@ -416,7 +416,7 @@ def test_config_key_deprecation():
 
     warning_message = """
 Valid config keys have changed in V2:
-* 'allow_population_by_field_name' has been renamed to 'populate_by_name'
+* 'allow_population_by_field_name' has been renamed to 'validate_by_name'
 * 'anystr_lower' has been renamed to 'str_to_lower'
 * 'anystr_strip_whitespace' has been renamed to 'str_strip_whitespace'
 * 'anystr_upper' has been renamed to 'str_to_upper'
@@ -489,25 +489,25 @@ class Mixin(BaseModel):
         model_config = ConfigDict(use_enum_values=True)
 
     class Child(Mixin, Parent):
-        model_config = ConfigDict(populate_by_name=True)
+        model_config = ConfigDict(validate_by_name=True)
 
     assert BaseModel.model_config.get('frozen') is None
-    assert BaseModel.model_config.get('populate_by_name') is None
+    assert BaseModel.model_config.get('validate_by_name') is None
     assert BaseModel.model_config.get('extra') is None
     assert BaseModel.model_config.get('use_enum_values') is None
 
     assert Parent.model_config.get('frozen') is True
-    assert Parent.model_config.get('populate_by_name') is None
+    assert Parent.model_config.get('validate_by_name') is None
     assert Parent.model_config.get('extra') == 'forbid'
     assert Parent.model_config.get('use_enum_values') is None
 
     assert Mixin.model_config.get('frozen') is None
-    assert Mixin.model_config.get('populate_by_name') is None
+    assert Mixin.model_config.get('validate_by_name') is None
     assert Mixin.model_config.get('extra') is None
     assert Mixin.model_config.get('use_enum_values') is True
 
     assert Child.model_config.get('frozen') is True
-    assert Child.model_config.get('populate_by_name') is True
+    assert Child.model_config.get('validate_by_name') is True
     assert Child.model_config.get('extra') == 'forbid'
     assert Child.model_config.get('use_enum_values') is True
 
@@ -944,3 +944,22 @@ def test_generate_schema_deprecation_warning() -> None:
 
         class Model(BaseModel):
             model_config = ConfigDict(schema_generator=GenerateSchema)
+
+
+def test_populate_by_name_still_effective() -> None:
+    class Model(BaseModel):
+        model_config = ConfigDict(populate_by_name=True)
+
+        a: int = Field(alias='A')
+
+    assert Model.model_validate({'A': 1}).a == 1
+    assert Model.model_validate({'a': 1}).a == 1
+
+
+def test_user_error_on_alias_settings() -> None:
+    with pytest.raises(
+        PydanticUserError, match='At least one of `validate_by_alias` or `validate_by_name` must be set to True.'
+    ):
+
+        class Model(BaseModel):
+            model_config = ConfigDict(validate_by_alias=False, validate_by_name=False)
diff --git a/tests/test_deprecated_validate_arguments.py b/tests/test_deprecated_validate_arguments.py
index 2dedc6ee0e1..53ce5749aa8 100644
--- a/tests/test_deprecated_validate_arguments.py
+++ b/tests/test_deprecated_validate_arguments.py
@@ -377,8 +377,8 @@ def foo(c: int = Field(default_factory=lambda: 20), a: int = Field(default_facto
     assert foo(b=10) == 30
 
 
-def test_populate_by_name():
-    @validate_arguments(config=dict(populate_by_name=True))
+def test_validate_by_name():
+    @validate_arguments(config=dict(validate_by_name=True, validate_by_alias=True))
     def foo(a: Annotated[int, Field(alias='b')], c: Annotated[int, Field(alias='d')]):
         return a + c
 
diff --git a/tests/test_discriminated_union.py b/tests/test_discriminated_union.py
index 3acadeba8b5..22616dddf54 100644
--- a/tests/test_discriminated_union.py
+++ b/tests/test_discriminated_union.py
@@ -347,7 +347,7 @@ class A(BaseModel):
         literal: Literal['a'] = Field(alias='lit')
 
     class B(BaseModel):
-        model_config = ConfigDict(populate_by_name=True)
+        model_config = ConfigDict(validate_by_name=True)
         literal: Literal['b'] = Field(alias='lit')
 
     class Top(BaseModel):
diff --git a/tests/test_main.py b/tests/test_main.py
index b459a73ef62..40ede96118d 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -475,7 +475,7 @@ class AnyModel(BaseModel):
 
 def test_population_by_field_name():
     class Model(BaseModel):
-        model_config = ConfigDict(populate_by_name=True)
+        model_config = ConfigDict(validate_by_name=True)
         a: str = Field(alias='_a')
 
     assert Model(a='different').a == 'different'
@@ -1101,7 +1101,7 @@ class ModelB(BaseModel):
 
 def test_dict_exclude_unset_populated_by_alias():
     class MyModel(BaseModel):
-        model_config = ConfigDict(populate_by_name=True)
+        model_config = ConfigDict(validate_by_name=True)
         a: str = Field('default', alias='alias_a')
         b: str = Field('default', alias='alias_b')
 
diff --git a/tests/test_model_signature.py b/tests/test_model_signature.py
index 8d8a8dae06a..0b15b7602dd 100644
--- a/tests/test_model_signature.py
+++ b/tests/test_model_signature.py
@@ -95,7 +95,7 @@ def test_use_field_name():
     class Foo(BaseModel):
         foo: str = Field(alias='this is invalid')
 
-        model_config = ConfigDict(populate_by_name=True)
+        model_config = ConfigDict(validate_by_name=True)
 
     assert _equals(str(signature(Foo)), '(*, foo: str) -> None')
 
@@ -104,7 +104,7 @@ def test_does_not_use_reserved_word():
     class Foo(BaseModel):
         from_: str = Field(alias='from')
 
-        model_config = ConfigDict(populate_by_name=True)
+        model_config = ConfigDict(validate_by_name=True)
 
     assert _equals(str(signature(Foo)), '(*, from_: str) -> None')
 
diff --git a/tests/test_networks.py b/tests/test_networks.py
index c28d333ad8e..17636f9131b 100644
--- a/tests/test_networks.py
+++ b/tests/test_networks.py
@@ -124,10 +124,13 @@ class Model(BaseModel):
     [
         ('http:///', 'url_parsing', 'Input should be a valid URL, empty host'),
         ('http://??', 'url_parsing', 'Input should be a valid URL, empty host'),
-        (
+        pytest.param(
             'https://example.org more',
             'url_parsing',
-            'Input should be a valid URL, invalid domain character',
+            'Input should be a valid URL, invalid international domain name',
+            marks=pytest.mark.skip(
+                reason='Skipping until pydantic-core version with url validation updates is available'
+            ),
         ),
         ('$https://example.org', 'url_parsing', 'Input should be a valid URL, relative URL without a base'),
         ('../icons/logo.gif', 'url_parsing', 'Input should be a valid URL, relative URL without a base'),
diff --git a/tests/test_plugins.py b/tests/test_plugins.py
index b30950c394a..1c02ed40e64 100644
--- a/tests/test_plugins.py
+++ b/tests/test_plugins.py
@@ -36,6 +36,8 @@ def on_enter(
             strict: bool | None = None,
             context: dict[str, Any] | None = None,
             self_instance: Any | None = None,
+            by_alias: bool | None = None,
+            by_name: bool | None = None,
         ) -> None:
             assert input == '{"a": 1}'
             assert strict is None
@@ -77,6 +79,8 @@ def on_enter(
             strict: bool | None = None,
             context: dict[str, Any] | None = None,
             self_instance: Any | None = None,
+            by_alias: bool | None = None,
+            by_name: bool | None = None,
         ) -> None:
             assert input == '{"a": "potato"}'
             assert strict is None
@@ -121,6 +125,8 @@ def on_enter(
             from_attributes: bool | None = None,
             context: dict[str, Any] | None = None,
             self_instance: Any | None = None,
+            by_alias: bool | None = None,
+            by_name: bool | None = None,
         ) -> None:
             assert input == {'a': 1}
             assert strict is None
@@ -158,6 +164,8 @@ def on_enter(
             from_attributes: bool | None = None,
             context: dict[str, Any] | None = None,
             self_instance: Any | None = None,
+            by_alias: bool | None = None,
+            by_name: bool | None = None,
         ) -> None:
             assert input == {'a': 'potato'}
             assert strict is None
@@ -206,6 +214,8 @@ def on_enter(
             from_attributes: bool | None = None,
             context: dict[str, Any] | None = None,
             self_instance: Any | None = None,
+            by_alias: bool | None = None,
+            by_name: bool | None = None,
         ) -> None:
             stack.append(input)
 
@@ -298,14 +308,14 @@ class Model(BaseModel):
         assert Model.model_validate_json('{"a": 2}', context={'c': 2}).model_dump() == {'a': 2}
         # insert_assert(log)
         assert log == [
-            "json enter input={\"a\": 2} kwargs={'strict': None, 'context': {'c': 2}}",
+            "json enter input={\"a\": 2} kwargs={'strict': None, 'context': {'c': 2}, 'by_alias': None, 'by_name': None}",
             'json success result=a=2',
         ]
         log.clear()
         assert Model.model_validate_strings({'a': '3'}, strict=True, context={'c': 3}).model_dump() == {'a': 3}
         # insert_assert(log)
         assert log == [
-            "strings enter input={'a': '3'} kwargs={'strict': True, 'context': {'c': 3}}",
+            "strings enter input={'a': '3'} kwargs={'strict': True, 'context': {'c': 3}, 'by_alias': None, 'by_name': None}",
             'strings success result=a=3',
         ]
 
diff --git a/tests/test_types.py b/tests/test_types.py
index 04bafe15983..3579fce33e8 100644
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -7111,7 +7111,7 @@ class MyBaseModel(BaseModel):
         model_config = ConfigDict(strict=True)
 
     class Model(MyBaseModel):
-        a: int | Annotated[tuple[int, int], Strict(False)] = Field(default=(1, 2))
+        a: Union[int, Annotated[tuple[int, int], Strict(False)]] = Field(default=(1, 2))
 
     m = Model(a=[1, 2])
     assert m.a == (1, 2)
diff --git a/tests/test_validate_call.py b/tests/test_validate_call.py
index 78f8a89f27b..c48051b544f 100644
--- a/tests/test_validate_call.py
+++ b/tests/test_validate_call.py
@@ -833,8 +833,8 @@ def foo(c: int = Field(default_factory=lambda: 20), a: int = Field(default_facto
     assert foo(b=10) == 30
 
 
-def test_populate_by_name():
-    @validate_call(config=dict(populate_by_name=True))
+def test_validate_by_name():
+    @validate_call(config=dict(validate_by_name=True))
     def foo(a: Annotated[int, Field(alias='b')], c: Annotated[int, Field(alias='d')]):
         return a + c
 


Code review comments:
path: pydantic/mypy.py
text: @user1:
I think you'll have to update the `collect_config()` logic to handle both `validate_by_name` and `populate_by_name`. Mypy does static analysis so it can't be aware of the added logic in `ConfigWrapper.core_config()`.

Also, I'm not sure if the plugin already handled this, but previously if `populate_by_name` was set, the following calls were allowed:

```python
class Model(BaseModel):
    field: int = Field(alias='alias')

    model_config = {'populate_by_name': True}

Model(field=1)  # OK
Model(alias=1)  # OK
```

If the plugin accepted both these calls, it will probably need to be updated to disallow the following:

```python
class Model(BaseModel):
    field: int = Field(alias='alias')

    model_config = {'validate_by_alias': True, 'validate_by_name': False}

Model(field=1)  # Type checker error
Model(alias=1)  # OK
```

@author:
Agreed, definitely important. I've pushed a [commit](https://github.com/pydantic/pydantic/pull/11468/commits/2341a58997683df514a7dcd27a615a5d05678f55) to make this backwards compatible.

The new behavior:

```py
from pydantic import BaseModel, ConfigDict, Field

class Model1(BaseModel):
    model_config = ConfigDict(validate_by_alias=False, validate_by_name=True)

    my_field: str = Field(alias='my_alias')

m1 = Model1(my_field='foo')

class Model2(BaseModel):
    model_config = ConfigDict(validate_by_alias=True, validate_by_name=False)

    my_field: str = Field(alias='my_alias')

m2 = Model2(my_alias='foo')

class Model3(BaseModel):
    model_config = ConfigDict(validate_by_alias=True, validate_by_name=True)

    my_field: str = Field(alias='my_alias')

# for this case, we prefer the field name over the alias, as we did in the past
# if populate_by_name was True
m31 = Model3(my_field='foo')
m32 = Model3(my_alias='foo')
#> test.py:23: error: Missing named argument "my_field" for "Model3"  [call-arg]

class Model4(BaseModel):
    my_field: str = Field(alias='my_alias')

m4 = Model4(my_alias='foo')

class Model5(BaseModel):
    model_config = ConfigDict(populate_by_name=True)

    my_field: str = Field(alias='my_alias')

# for this case, we prefer the field name over the alias
m51 = Model5(my_field='foo')
m52 = Model5(my_alias='foo')
#> test.py:39: error: Missing named argument "my_field" for "Model5"  [call-arg]
```

diff_hunk: @@ -343,7 +343,7 @@ will be merged, and for any settings defined in both, those from `Model2` will o
     * `post_init_call`
 
 * The following config settings have been renamed:
-    * `allow_population_by_field_name` → `populate_by_name`
+    * `allow_population_by_field_name` → `validate_by_name`
path: docs/migration.md
text: @user1:
```suggestion
    * `allow_population_by_field_name` → `populate_by_name` (or `validate_by_name` starting in v2.11)
```

diff_hunk: @@ -1381,3 +1381,25 @@ try:
 except PydanticUserError as exc_info:
     assert exc_info.code == 'invalid-self-type'
 ```
+
+## `validate_by_alias` and `validate_by_name` `False` {#validate-by-alias-and-name-false}
+
+This error is raised when you set `validate_by_alias` and `validate_by_name` to `False` in a model's configuration.
path: docs/errors/usage_errors.md
text: @user1:
```suggestion
This error is raised when you set `validate_by_alias` and `validate_by_name` to `False` in the configuration.
```

As configuration is not a concept limited to models.