Exception Groups Preserve Concurrent Failure Structure
How ExceptionGroup and except-star split, handle, and reassemble multiple errors without flattening context.
Concurrent operations can fail independently before a caller regains control. Raising only the first error loses information; flattening every error into a list loses traceback structure. ExceptionGroup represents a tree of exceptions under one message.
errors = [ValueError('bad port'), OSError('host unavailable')]
raise ExceptionGroup('configuration failed', errors)
An ExceptionGroup contains only Exception subclasses. BaseExceptionGroup can also contain exceptions such as KeyboardInterrupt, and its constructor may return the narrower ExceptionGroup when every child permits it.
except* selects subgroups
try:
await run_many_operations()
except* OSError as group:
for error in group.exceptions:
log_network_failure(error)
except* ValueError as group:
report_bad_input(group)
Each except* clause receives a subgroup that preserves the shape and metadata of the original group while containing matching leaves. It does not necessarily receive one bare exception at a time.
The clauses behave like ordinary except ordering: after the first except* matches any leaf, no later except* clause runs. Put the most specific handler first and a broad handler last.
Exceptions raised inside a handler are combined with unhandled portions rather than treated as if they were original leaves. Traceback, cause, context, and notes help the runtime distinguish newly raised errors from reraised subgroups.
Normal except sees the group as one exception
A regular except Exception can never catch an ExceptionGroup; grouped exceptions require except*. This prevents old error handlers from accidentally swallowing multiple failures they were not designed to process.
The syntax does not allow return, break, or continue inside an except* suite because each handler operates independently over a subgroup. It is also a syntax error to mix ordinary except and except* clauses in the same try statement.
APIs should preserve meaning
Task groups use exception groups because sibling tasks can fail during the same shutdown. Validation libraries may also group independent field failures. Callers can derive subgroups with methods such as subgroup and split without discarding the original tree.
Do not create a group merely to avoid choosing a primary exception in sequential code. Use it when failures are genuinely peers and callers benefit from handling some categories while allowing others to propagate.
The model is structured partitioning: matching leaves retain their path through the group, handlers see relevant subtrees, and unhandled structure survives. That is more information than either “first failure wins” or a flat list can provide.