skip to content

Why can't a generic class in Java extend Throwable (e.g. why is `class MyException<T> extends Exception` illegal)?

level: middleimportance: should knowfreq 35%

answer

  1. catch dispatch = runtime type test
  2. erasure deletes <T> at runtime
  3. MyException<Int> and MyException<String> collapse to raw
  4. JLS 8.1.2 forbids generic Throwable subclass
  5. compile error even if T is never used in catch

basics

~20 s

Java forbids it. A type that can be thrown and caught must be a real, fixed type at runtime, but generics are erased and lose their type info, so the compiler blocks any generic subclass of Throwable.

solid answer

~40 s

Exception handling works at runtime: when something is thrown, the JVM walks the stack and tests each `catch` clause against the runtime type of the thrown object. That test needs a concrete, reifiable type. Generics in Java use type erasure, so a parameter like `T` (or `MyException<String>` vs `MyException<Integer>`) does not exist at runtime — both collapse to the raw type. The JVM could not tell them apart, so catch logic would be ambiguous. To avoid this broken situation, the language designers simply made it a compile error for any class that is, directly or indirectly, a subtype of `Throwable` to be generic. So `class Foo<T> extends Exception {}` does not compile, regardless of how `T` is used.

code

java · 9 lines
java
// Does NOT compile: a generic class cannot be a Throwable subtype.
// class MyException<T> extends Exception {}  // error: generic class may not extend Throwable

// Fine: ordinary non-generic exception.
class MyException extends Exception {
    private final Object payload; // carry context as a plain field instead
    MyException(String msg, Object payload) { super(msg); this.payload = payload; }
    Object payload() { return payload; }
}

go deeper

for a junior

Knows that you simply cannot make an exception class generic and that it's a compile error.

for a middle

Explains it via type erasure: <T> disappears at runtime so the catch machinery can't distinguish parameterized exception types.

for a senior

Connects erasure, reifiable types, and runtime catch dispatch; notes JLS 8.1.2 and that the rule is unconditional, and gives the legal alternatives.

for a principal

Frames it as a deliberate language-design trade-off (erasure for migration compatibility) and can reason about how a reified-generics language could lift the restriction and what that would cost.

## The terms first - **Throwable**: the root class of everything Java can throw/catch. `Exception` and `Error` extend it. - **Generics**: a way to parameterize a class or method by a type, e.g. `List<String>`. The `<T>` is a *type parameter*. - **Type erasure**: Java generics are a *compile-time* feature. After the compiler checks your types, it **erases** them — `List<String>` and `List<Integer>` both become plain `List` in the bytecode. The runtime has no idea what `T` was. This was done so generics could be added to Java 5 without breaking older bytecode. - **Reifiable type**: a type whose full information *survives* to runtime (e.g. `String`, `int[]`, `List` raw, `List<?>`). A type like `List<String>` is **non-reifiable** because the `<String>` part is erased. ## Why exceptions need reifiable types When code does `throw e`, the JVM searches up the call stack for a matching `catch`. Matching means: *is the runtime class of `e` assignable to the type in this catch clause?* This is a runtime type test. It can only work if the catch type is one the JVM can actually check at runtime — a reifiable type. ## Putting it together If generic exceptions were allowed, you could write: ```java try { ... } catch (MyException<Integer> e) { ... } catch (MyException<String> e) { ... } ``` After erasure, **both** catch types become the raw `MyException`. The JVM cannot tell an `Integer`-flavored instance from a `String`-flavored one — that information is gone. The two catch clauses would be indistinguishable and the dispatch would be ambiguous/meaningless. Rather than allow some half-broken subset, the Java Language Specification (JLS §8.1.2) makes it a **compile error** for a generic class to be a subtype of `Throwable` — *whether or not* you ever write `catch (MyException<Integer>)`. So even `class Foo<T> extends Exception {}` fails to compile. ## What you CAN do - A non-generic exception is fine: `class MyException extends Exception {}`. - A generic method or class may *declare and use* a type parameter that happens to be bounded by `Throwable`, e.g. `<T extends Throwable> void f(T t)` — that's allowed. The restriction is specifically on a *class definition* extending Throwable while being generic. - You can store extra typed payload on an exception via a normal (non-generic) field, or carry context another way. The takeaway: throwing/catching is a runtime mechanism; generics are erased before runtime; so the two are fundamentally incompatible, and the language forbids the combination up front.

  • Is `class Foo<T> extends Exception {}` rejected even if `T` is never used anywhere?
    Yes. The rule is structural: any generic class that is a subtype of Throwable is a compile error, regardless of whether the type parameter is used. The compiler does not analyze usage.
  • Can a generic method have a type parameter bounded by Throwable?
    Yes. `<T extends Throwable> void rethrow(T t) throws T` is legal — the restriction is on a generic *class extending Throwable*, not on using a Throwable-bounded type variable in a method.

It's like printing two parcels with the same shipping label but writing the real address only in invisible ink that fades before delivery. The sorter (JVM) can only read the printed label (erased raw type), so it can't route them differently.

saying these in an interview costs you the question

  • Claiming it's allowed but 'just a bad idea' — it is an actual compile error.
  • Saying the restriction only applies when T is used in a catch — it applies to the class definition unconditionally.
  • Confusing this with generic methods that have Throwable-bounded parameters, which ARE allowed.
  • Attributing it to the JVM rather than the language spec / erasure model.

context