# Python 3.15 makes UTF-8 the default encoding


Python 3.15, due as a release candidate on August 4, 2026, ships [PEP 686](https://peps.python.org/pep-0686/) enabled by default. Every text-mode `open()` call and every `subprocess.run(text=True)` without an explicit `encoding=` argument will use UTF-8 instead of the system locale encoding. On Windows, redirected `sys.stdin`, `sys.stdout`, and `sys.stderr` are also affected; streams attached to a console already used UTF-8 before this change.

On macOS and Linux, UTF-8 locales are already standard, so almost nothing changes in practice. On Windows, where the system code page is typically cp1252, cp932, or a regional variant, any code that omits `encoding=` will behave differently after the upgrade.

## Find code that depends on the locale encoding

Python 3.10 added `EncodingWarning` to identify calls that omit an explicit encoding. Run your test suite or a script with the warning enabled:

```bash
python -X warn_default_encoding your_script.py
```

Or set the environment variable before running:

```bash
PYTHONWARNDEFAULTENCODING=1 python -m pytest
```

Every text-mode `open()` call that omits `encoding=` raises this warning; binary `open()` calls and standard-stream access are not flagged. On macOS and Linux, warnings still appear for locale-dependent calls even though the locale is already UTF-8, which makes the output useful for writing cross-platform code.

## Fix the callsites

Each warning points to a choice between two explicit values:

```python
# Correct for config files, source code, JSON, and network data
with open("data.json", encoding="utf-8") as f:
    content = f.read()

# Correct for files that must match the user's system encoding
with open("report.txt", encoding="locale") as f:
    content = f.read()
```

The `encoding="locale"` option was added in Python 3.10 and correctly preserves locale behavior in UTF-8 mode as of Python 3.11 (an earlier bug was fixed in that release). It works in `subprocess.run()` as well:

```python
# Preserve old locale behavior in subprocess calls
result = subprocess.run(["tool"], capture_output=True, text=True, encoding="locale")
```

If your code calls `locale.getpreferredencoding()` to discover the system encoding, replace it with `locale.getencoding()`. In UTF-8 mode, `getpreferredencoding()` returns `'UTF-8'` regardless of the locale; `getencoding()`, added in Python 3.11, always returns the locale encoding.

## Opt out during migration

`PYTHONUTF8=0` disables UTF-8 mode and restores locale encoding as the default. Add it to your deployment environment while auditing your callsites:

```bash
PYTHONUTF8=0 python your_script.py
```

The `-X utf8=0` command-line flag does the same thing inline. Both work as a stopgap during migration; the long-term fix is explicit `encoding=` arguments throughout the code.

## Learn more

- [PEP 686 – Make UTF-8 Mode default](https://peps.python.org/pep-0686/) specifies every interface affected by UTF-8 mode
- [What's New in Python 3.15](https://docs.python.org/3.15/whatsnew/3.15.html) covers this change alongside [explicit lazy imports](https://pydevtools.com/handbook/explanation/what-is-pep-810.md) for faster CLI startup
- [Try free-threaded Python with uv](https://pydevtools.com/handbook/tutorial/try-free-threaded-python-with-uv.md) covers free-threaded Python, which shipped experimentally in 3.13, became officially supported in 3.14, and continues to mature in 3.15
