### Parser
 
+- [PEP 654](https://peps.python.org/pep-0654/#except) syntax (for example,
+  `except *ExceptionGroup:`) is now supported (#3016)
+
 <!-- Changes to the parser or to version autodetection -->
 
 ### Performance
 
         ):
             features.add(Feature.ANN_ASSIGN_EXTENDED_RHS)
 
+        elif (
+            n.type == syms.except_clause
+            and len(n.children) >= 2
+            and n.children[1].type == token.STAR
+        ):
+            features.add(Feature.EXCEPT_STAR)
+
     return features
 
 
 
                     node.insert_child(index, Leaf(token.LPAR, ""))
                     node.append_child(Leaf(token.RPAR, ""))
                 break
+            elif (
+                index == 1
+                and child.type == token.STAR
+                and node.type == syms.except_clause
+            ):
+                # In except* (PEP 654), the star is actually part of
+                # of the keyword. So we need to skip the insertion of
+                # invisible parentheses to work more precisely.
+                continue
 
             elif not (isinstance(child, Leaf) and is_multiline_string(child)):
                 wrap_in_parentheses(node, child, visible=False)
 
     PY38 = 8
     PY39 = 9
     PY310 = 10
+    PY311 = 11
 
 
 class Feature(Enum):
     PATTERN_MATCHING = 11
     UNPACKING_ON_FLOW = 12
     ANN_ASSIGN_EXTENDED_RHS = 13
+    EXCEPT_STAR = 14
     FORCE_OPTIONAL_PARENTHESES = 50
 
     # __future__ flags
         Feature.ANN_ASSIGN_EXTENDED_RHS,
         Feature.PATTERN_MATCHING,
     },
+    TargetVersion.PY311: {
+        Feature.F_STRINGS,
+        Feature.NUMERIC_UNDERSCORES,
+        Feature.TRAILING_COMMA_IN_CALL,
+        Feature.TRAILING_COMMA_IN_DEF,
+        Feature.ASYNC_KEYWORDS,
+        Feature.FUTURE_ANNOTATIONS,
+        Feature.ASSIGNMENT_EXPRESSIONS,
+        Feature.RELAXED_DECORATORS,
+        Feature.POS_ONLY_ARGUMENTS,
+        Feature.UNPACKING_ON_FLOW,
+        Feature.ANN_ASSIGN_EXTENDED_RHS,
+        Feature.PATTERN_MATCHING,
+        Feature.EXCEPT_STAR,
+    },
 }
 
 
 
     elif p.type == syms.sliceop:
         return NO
 
+    elif p.type == syms.except_clause:
+        if t == token.STAR:
+            return NO
+
     return SPACE
 
 
 
 with_stmt: 'with' asexpr_test (',' asexpr_test)*  ':' suite
 
 # NB compile.c makes sure that the default except clause is last
-except_clause: 'except' [test [(',' | 'as') test]]
+except_clause: 'except' ['*'] [test [(',' | 'as') test]]
 suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
 
 # Backward compatibility cruft to support:
 
--- /dev/null
+try:
+    raise OSError("blah")
+except* ExceptionGroup as e:
+    pass
+
+
+try:
+    async with trio.open_nursery() as nursery:
+        # Make two concurrent calls to child()
+        nursery.start_soon(child)
+        nursery.start_soon(child)
+except* ValueError:
+    pass
+
+try:
+    try:
+        raise ValueError(42)
+    except:
+        try:
+            raise TypeError(int)
+        except* Exception:
+            pass
+        1 / 0
+except Exception as e:
+    exc = e
+
+try:
+    try:
+        raise FalsyEG("eg", [TypeError(1), ValueError(2)])
+    except* TypeError as e:
+        tes = e
+        raise
+    except* ValueError as e:
+        ves = e
+        pass
+except Exception as e:
+    exc = e
+
+try:
+    try:
+        raise orig
+    except* (TypeError, ValueError) as e:
+        raise SyntaxError(3) from e
+except BaseException as e:
+    exc = e
+
+try:
+    try:
+        raise orig
+    except* OSError as e:
+        raise TypeError(3) from e
+except ExceptionGroup as e:
+    exc = e
 
--- /dev/null
+try:
+    raise OSError("blah")
+except               * ExceptionGroup as e:
+    pass
+
+
+try:
+    async with trio.open_nursery() as nursery:
+        # Make two concurrent calls to child()
+        nursery.start_soon(child)
+        nursery.start_soon(child)
+except *ValueError:
+    pass
+
+try:
+    try:
+        raise ValueError(42)
+    except:
+        try:
+            raise TypeError(int)
+        except *(Exception):
+            pass
+        1 / 0
+except Exception as e:
+    exc = e
+
+try:
+    try:
+        raise FalsyEG("eg", [TypeError(1), ValueError(2)])
+    except \
+        *TypeError as e:
+        tes = e
+        raise
+    except  *  ValueError as e:
+        ves = e
+        pass
+except Exception as e:
+    exc = e
+
+try:
+    try:
+        raise orig
+    except *(TypeError, ValueError, *OTHER_EXCEPTIONS) as e:
+        raise SyntaxError(3) from e
+except BaseException as e:
+    exc = e
+
+try:
+    try:
+        raise orig
+    except\
+        * OSError as e:
+        raise TypeError(3) from e
+except ExceptionGroup as e:
+    exc = e
+
+# output
+
+try:
+    raise OSError("blah")
+except* ExceptionGroup as e:
+    pass
+
+
+try:
+    async with trio.open_nursery() as nursery:
+        # Make two concurrent calls to child()
+        nursery.start_soon(child)
+        nursery.start_soon(child)
+except* ValueError:
+    pass
+
+try:
+    try:
+        raise ValueError(42)
+    except:
+        try:
+            raise TypeError(int)
+        except* (Exception):
+            pass
+        1 / 0
+except Exception as e:
+    exc = e
+
+try:
+    try:
+        raise FalsyEG("eg", [TypeError(1), ValueError(2)])
+    except* TypeError as e:
+        tes = e
+        raise
+    except* ValueError as e:
+        ves = e
+        pass
+except Exception as e:
+    exc = e
+
+try:
+    try:
+        raise orig
+    except* (TypeError, ValueError, *OTHER_EXCEPTIONS) as e:
+        raise SyntaxError(3) from e
+except BaseException as e:
+    exc = e
+
+try:
+    try:
+        raise orig
+    except* OSError as e:
+        raise TypeError(3) from e
+except ExceptionGroup as e:
+    exc = e
 
         self.assertEqual(
             black.get_features_used(node), {Feature.ANN_ASSIGN_EXTENDED_RHS}
         )
+        node = black.lib2to3_parse("try: pass\nexcept Something: pass")
+        self.assertEqual(black.get_features_used(node), set())
+        node = black.lib2to3_parse("try: pass\nexcept (*Something,): pass")
+        self.assertEqual(black.get_features_used(node), set())
+        node = black.lib2to3_parse("try: pass\nexcept *Group: pass")
+        self.assertEqual(black.get_features_used(node), {Feature.EXCEPT_STAR})
 
     def test_get_features_used_for_future_flags(self) -> None:
         for src, features in [
 
     "parenthesized_context_managers",
 ]
 
+PY311_CASES: List[str] = [
+    "pep_654",
+    "pep_654_style",
+]
+
 PREVIEW_CASES: List[str] = [
     # string processing
     "cantfit",
     exc_info.match("Cannot parse: 10:11")
 
 
+@pytest.mark.parametrize("filename", PY311_CASES)
+def test_python_311(filename: str) -> None:
+    source, expected = read_data(filename)
+    mode = black.Mode(target_versions={black.TargetVersion.PY311})
+    assert_format(source, expected, mode, minimum_version=(3, 11))
+
+
 def test_python_2_hint() -> None:
     with pytest.raises(black.parsing.InvalidInput) as exc_info:
         assert_format("print 'daylily'", "print 'daylily'")