diff --git a/pydantic_settings/sources.py b/pydantic_settings/sources.py
index c7c019a..23bbbf1 100644
--- a/pydantic_settings/sources.py
+++ b/pydantic_settings/sources.py
@@ -753,6 +753,30 @@ def __init__(
     def _load_env_vars(self) -> Mapping[str, str | None]:
         return self._read_env_files()
 
+    @staticmethod
+    def _static_read_env_file(
+        file_path: Path,
+        *,
+        encoding: str | None = None,
+        case_sensitive: bool = False,
+        ignore_empty: bool = False,
+        parse_none_str: str | None = None,
+    ) -> Mapping[str, str | None]:
+        file_vars: dict[str, str | None] = dotenv_values(file_path, encoding=encoding or 'utf8')
+        return parse_env_vars(file_vars, case_sensitive, ignore_empty, parse_none_str)
+
+    def _read_env_file(
+        self,
+        file_path: Path,
+    ) -> Mapping[str, str | None]:
+        return self._static_read_env_file(
+            file_path,
+            encoding=self.env_file_encoding,
+            case_sensitive=self.case_sensitive,
+            ignore_empty=self.env_ignore_empty,
+            parse_none_str=self.env_parse_none_str,
+        )
+
     def _read_env_files(self) -> Mapping[str, str | None]:
         env_files = self.env_file
         if env_files is None:
@@ -765,15 +789,7 @@ def _read_env_files(self) -> Mapping[str, str | None]:
         for env_file in env_files:
             env_path = Path(env_file).expanduser()
             if env_path.is_file():
-                dotenv_vars.update(
-                    read_env_file(
-                        env_path,
-                        encoding=self.env_file_encoding,
-                        case_sensitive=self.case_sensitive,
-                        ignore_empty=self.env_ignore_empty,
-                        parse_none_str=self.env_parse_none_str,
-                    )
-                )
+                dotenv_vars.update(self._read_env_file(env_path))
 
         return dotenv_vars
 
@@ -1708,8 +1724,17 @@ def read_env_file(
     ignore_empty: bool = False,
     parse_none_str: str | None = None,
 ) -> Mapping[str, str | None]:
-    file_vars: dict[str, str | None] = dotenv_values(file_path, encoding=encoding or 'utf8')
-    return parse_env_vars(file_vars, case_sensitive, ignore_empty, parse_none_str)
+    warnings.warn(
+        'read_env_file will be removed in the next version, use DotEnvSettingsSource._static_read_env_file if you must',
+        DeprecationWarning,
+    )
+    return DotEnvSettingsSource._static_read_env_file(
+        file_path,
+        encoding=encoding,
+        case_sensitive=case_sensitive,
+        ignore_empty=ignore_empty,
+        parse_none_str=parse_none_str,
+    )
 
 
 def _annotation_is_complex(annotation: type[Any] | None, metadata: list[Any]) -> bool:
diff --git a/tests/test_settings.py b/tests/test_settings.py
index 7690e3c..e812ffe 100644
--- a/tests/test_settings.py
+++ b/tests/test_settings.py
@@ -34,7 +34,7 @@
 from pydantic._internal._repr import Representation
 from pydantic.fields import FieldInfo
 from pytest_mock import MockerFixture
-from typing_extensions import Annotated, Literal
+from typing_extensions import Annotated, Literal, override
 
 from pydantic_settings import (
     BaseSettings,
@@ -49,7 +49,7 @@
     TomlConfigSettingsSource,
     YamlConfigSettingsSource,
 )
-from pydantic_settings.sources import CliPositionalArg, CliSettingsSource, CliSubCommand, SettingsError, read_env_file
+from pydantic_settings.sources import CliPositionalArg, CliSettingsSource, CliSubCommand, SettingsError
 
 try:
     import dotenv
@@ -1039,15 +1039,15 @@ def test_read_env_file_case_sensitive(tmp_path):
     p = tmp_path / '.env'
     p.write_text('a="test"\nB=123')
 
-    assert read_env_file(p) == {'a': 'test', 'b': '123'}
-    assert read_env_file(p, case_sensitive=True) == {'a': 'test', 'B': '123'}
+    assert DotEnvSettingsSource._static_read_env_file(p) == {'a': 'test', 'b': '123'}
+    assert DotEnvSettingsSource._static_read_env_file(p, case_sensitive=True) == {'a': 'test', 'B': '123'}
 
 
 def test_read_env_file_syntax_wrong(tmp_path):
     p = tmp_path / '.env'
     p.write_text('NOT_AN_ASSIGNMENT')
 
-    assert read_env_file(p, case_sensitive=True) == {'NOT_AN_ASSIGNMENT': None}
+    assert DotEnvSettingsSource._static_read_env_file(p, case_sensitive=True) == {'NOT_AN_ASSIGNMENT': None}
 
 
 def test_env_file_example(tmp_path):
@@ -1188,6 +1188,37 @@ def test_read_dotenv_vars_when_env_file_is_none():
     )
 
 
+def test_dotenvsource_override(env):
+    class StdinDotEnvSettingsSource(DotEnvSettingsSource):
+        @override
+        def _read_env_file(self, file_path: Path) -> Dict[str, str]:
+            assert str(file_path) == '-'
+            return {'foo': 'stdin_foo', 'bar': 'stdin_bar'}
+
+        @override
+        def _read_env_files(self) -> Dict[str, str]:
+            return self._read_env_file(Path('-'))
+
+    source = StdinDotEnvSettingsSource(BaseSettings())
+    assert source._read_env_files() == {'foo': 'stdin_foo', 'bar': 'stdin_bar'}
+
+
+# test that calling read_env_file issues a DeprecationWarning
+# TODO: remove this test once read_env_file is removed
+def test_read_env_file_deprecation(tmp_path):
+    from pydantic_settings.sources import read_env_file
+
+    base_env = tmp_path / '.env'
+    base_env.write_text(test_default_env_file)
+
+    with pytest.deprecated_call():
+        assert read_env_file(base_env) == {
+            'debug_mode': 'true',
+            'host': 'localhost',
+            'port': '8000',
+        }
+
+
 @pytest.mark.skipif(yaml, reason='PyYAML is installed')
 def test_yaml_not_installed(tmp_path):
     p = tmp_path / '.env'