ExceptionGroup.split: Split Exceptions by Type

Published on: October 28, 2026
Reading time: 5 minutes
Python code with grouped exception handling

ExceptionGroup.split() is a Python feature for separating a group of exceptions into two parts: exceptions that match a condition and exceptions that do not. It is particularly useful in applications that run several tasks, process many records, or combine results from multiple services. In these scenarios, more than one operation can fail at the same time. Keeping those failures in a structured group makes it possible to inspect the full picture without losing context.

What ExceptionGroup represents

An ExceptionGroup stores multiple related exceptions under one message. Consider a batch that validates many files. One file may be missing, another may have insufficient permissions, and another may contain invalid data. Instead of stopping at the first problem, the program can collect the failures and raise one group. Every individual exception remains available for logging, diagnosis, reporting, and recovery.

This model is valuable in concurrent code, but it is not limited to concurrency. It also works well for data imports, validation pipelines, test suites, API aggregation, and any workflow where independent operations can fail separately. The purpose is not to hide exceptions. The purpose is to organize them while preserving evidence about every failure.

How split works

The split() method accepts an exception type, a tuple of exception types, or a callable used as a filter. It returns a two-item tuple. The first item contains matching exceptions, while the second item contains everything else. If one side has no exceptions, that side is returned as None.

group = ExceptionGroup(
    "validation failures",
    [ValueError("invalid age"), TypeError("wrong type")],
)

value_errors, others = group.split(ValueError)

In this example, value_errors contains the ValueError, and others contains the TypeError. The original group is not modified. Python creates derived groups while preserving the relevant message and hierarchy.

Filtering multiple exception types

You can pass a tuple when several exception types belong to the same operational category.

file_errors, remaining = group.split(
    (FileNotFoundError, PermissionError)
)

This pattern separates file-system problems from validation or programming errors. It is usually clearer than manually iterating through every exception and building lists yourself.

Using a callable as the condition

The condition may also be a function that receives each exception and returns True or False. This is useful when the decision depends on custom attributes, status codes, retry flags, or domain-specific rules.

class ServiceError(Exception):
    def __init__(self, status, message):
        super().__init__(message)
        self.status = status


def is_temporary(exc):
    return isinstance(exc, ServiceError) and exc.status >= 500

retry_group, permanent_group = group.split(is_temporary)

With this design, only temporary service failures are sent to a retry queue. Permanent failures remain available for reporting or manual correction.

Practical batch-validation example

Suppose a function validates many records and should report every problem found rather than only the first one.

def validate_records(records):
    errors = []

    for index, record in enumerate(records):
        try:
            if "email" not in record:
                raise KeyError(f"record {index}: missing email")
            if record.get("age", 0) < 0:
                raise ValueError(f"record {index}: invalid age")
        except Exception as exc:
            errors.append(exc)

    if errors:
        raise ExceptionGroup("record validation errors", errors)

The calling layer can separate missing-field errors from invalid-value errors. Different messages can then be shown to users, and each category can be logged in a suitable format.

try:
    validate_records(records)
except ExceptionGroup as group:
    missing, remaining = group.split(KeyError)

    if missing:
        log_missing_fields(missing)

    if remaining:
        raise remaining

The most important detail is to preserve or re-raise the remaining group. Handling one known category must not silently discard unrelated failures.

Nested exception groups

Exception groups may contain other groups. This commonly happens when each pipeline stage or worker creates its own group. The split() method preserves that tree structure.

group = ExceptionGroup(
    "pipeline",
    [
        ExceptionGroup("download", [TimeoutError("timed out")]),
        ExceptionGroup("parsing", [ValueError("invalid JSON")]),
    ],
)

After splitting by TimeoutError, the matching result still shows that the exception came from the download stage. That context is essential when diagnosing complex workflows.

split compared with subgroup

The subgroup() method returns only the matching portion. The split() method returns both the matching portion and the remainder. Choose subgroup() when you only need to inspect one subset. Choose split() when you need to process one category and continue handling or re-raising everything else.

Relationship with except star

Python also provides except* for handling exception groups declaratively. The two approaches are complementary. Use except* when normal exception-handling syntax expresses the flow clearly. Use split() when the program needs to store, transform, route, or inspect groups programmatically before deciding what to do.

Good practices

Prefer filtering by stable exception types or attributes. Error messages can change, may be translated, and sometimes include variable data. Avoid using text as the only criterion. Keep groups focused on one operation or logical unit. A very broad group containing unrelated failures becomes difficult to understand.

Log enough context before applying recovery steps. Useful fields include a task identifier, record number, file path, service name, and correlation identifier. Do not use split() as a mechanism for silently ignoring errors. Every remaining exception should be logged, returned, converted intentionally, or re-raised.

Testing split behavior

Tests should verify both returned groups, the exception types inside them, and the preservation of nested structure.

def test_split_by_type():
    group = ExceptionGroup(
        "test",
        [ValueError("a"), TypeError("b")],
    )

    values, others = group.split(ValueError)

    assert values is not None
    assert others is not None
    assert isinstance(values.exceptions[0], ValueError)
    assert isinstance(others.exceptions[0], TypeError)

Also test a condition with no matches and a condition where every exception matches. These cases confirm when one side of the returned tuple becomes None.

When this method is useful

ExceptionGroup.split() is a strong choice for batch processing, concurrent tasks, data pipelines, API fan-out, form validation, imports, and composite tests. For simple functions that can only fail once at a time, traditional exception handling is often sufficient and easier to read.

Common mistakes

A frequent mistake is assuming the result is a flat list. It is still an exception-group structure. Another mistake is treating the matching group but forgetting the remainder. A third mistake is creating one enormous group for unrelated subsystems. Smaller, contextual groups produce clearer logs and safer recovery logic.

Conclusion

ExceptionGroup.split() provides a precise way to classify multiple failures without losing their origin. It separates exceptions by type or condition, preserves nested groups, and supports selective recovery while keeping unknown failures visible. Continue learning with Academify articles about try and except, traceback, asyncio, and common Python errors. See the official Python documentation and PEP 654 for the formal behavior.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Python code with type annotations on a computer screen
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    evaluate_forward_ref: Resolve Forward References

    Learn typing.evaluate_forward_ref for safe forward-reference resolution, namespaces, caching, compatibility, and testing.

    Ler mais

    Tempo de leitura: 6 minutos
    28/10/2026
    Python code and environment variables
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    os.reload_environ: Refresh Environment Variables

    Learn how os.reload_environ refreshes environment variables, when it is useful, and how to avoid concurrency and validation problems.

    Ler mais

    Tempo de leitura: 5 minutos
    27/10/2026
    Python code with RLock synchronization
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    RLock.locked: Inspect Reentrant Locks

    Learn Python RLock.locked for diagnosing reentrant locks, testing releases, and avoiding check-then-act races.

    Ler mais

    Tempo de leitura: 5 minutos
    27/10/2026
    Python code showing warnings and diagnostics for developers
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    skip_file_prefixes: Point Warnings to User Code

    Learn how skip_file_prefixes makes Python warnings point to consumer code and improves library deprecation messages.

    Ler mais

    Tempo de leitura: 5 minutos
    26/10/2026
    Secure document folder representing safe TAR extraction in Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Safe TAR Extraction with Python tarfile

    Learn safe TAR extraction in Python with tarfile filters, size limits, path validation, and protection against path traversal.

    Ler mais

    Tempo de leitura: 4 minutos
    26/10/2026
    Dual monitors running Python code in parallel
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    concurrent.interpreters: Isolated Parallel Workers

    Learn Python concurrent.interpreters for isolated workers, explicit data transfer, and multicore parallelism.

    Ler mais

    Tempo de leitura: 6 minutos
    25/10/2026