Why can't you write a `catch (T e)` clause where T is a type parameter, and what does the compiler say?
answer
- catch needs a reifiable class
- T erases to its bound, so no class constant
- compile error: 'cannot use type parameter T in catch'
- asymmetry: throws T / throw t OK, catch (T) not
- enables sneaky-throw
basics
~20 sYou can't catch a type variable. A catch clause needs a concrete exception type the JVM can test at runtime, but T is erased and not known at runtime, so the compiler rejects catch (T e).
solid answer
~40 sA `catch` clause must name a *reifiable* class — one whose identity survives to runtime — because catching is a runtime test of the thrown object's class against the clause's type. A type parameter `T` is erased: at runtime it's just its bound (often `Throwable` or `Object`), and the specific `T` is unknown. So `catch (T e)` can't be implemented faithfully — there's no concrete type to compare against. The compiler reports something like *'Cannot use the type parameter T in a catch block'* (or 'unexpected type'). Note the asymmetry: you *can* declare `throws T` and even rethrow a `T`, because throwing doesn't require distinguishing erased parameterizations the way catching does. So generic methods can propagate a type-variable exception, but they cannot catch it by its type variable.
code
java · 8 lines<T extends Throwable> void demo() throws T {
try {
mightFail();
} catch (Exception e) { // OK: concrete reifiable type
// catch (T e) {} // ERROR: cannot use type parameter T in catch
throw (T) e; // throwing/declaring T is allowed
}
}go deeper
Knows catch (T e) doesn't compile and that you should catch a real exception class instead.
Explains that T is erased so there's no concrete type for the catch to test against at runtime.
Articulates the throw-vs-catch asymmetry, the exact reason (runtime type test vs erasure), and how to work around it by catching a bound.
Can derive sneaky-throw from the asymmetry, discuss the bytecode exception table, and weigh whether the convenience/danger trade-off is acceptable in a codebase.
## Terms - **Type parameter / type variable (`T`)**: a placeholder type in generic code, e.g. the `T` in `<T extends Throwable> void f()`. - **`catch` clause**: `catch (SomeType e)` — runs when a thrown object's runtime class is a `SomeType`. - **Type erasure**: generics are checked at compile time then erased; `T` is replaced by its *bound* (the rightmost type you constrained it to, or `Object` if none). So `<T extends Throwable>` becomes `Throwable` in bytecode. - **Reifiable type**: a type fully known at runtime. `T` is not reifiable — its concrete identity is gone. ## What catching actually requires When an exception is thrown, the JVM tests each enclosing `catch` type against the runtime class of the thrown object. To do that, the catch type has to be a real class the verifier and runtime can name. A `catch` is compiled into an *exception table* entry that references a concrete class constant. There is no class constant for `T` — after erasure `T` is just `Throwable`/`Object`, which would over-catch and defeat the purpose. So the language disallows `catch (T e)` entirely. ```java <T extends Throwable> void run() { try { risky(); } catch (T e) { // COMPILE ERROR: cannot use type parameter T in catch // ... } } ``` The compiler message is typically *'Cannot use the type parameter T in a catch block'* or *'unexpected type / required: class'*. ## The asymmetry: throwing is OK, catching is not Throwing and declaring are allowed: ```java <T extends Throwable> void rethrow(T t) throws T { throw t; // legal: just throws the object } ``` Why the difference? `throw t` simply hands the object to the JVM — it doesn't need to *distinguish* erased parameterizations. `throws T` is a compile-time declaration that's also erased to `throws Throwable`. Neither requires a runtime type test keyed on `T`. Catching does, and that's exactly what erasure cannot support. ## The 'sneaky throw' trick (consequence) This asymmetry enables the infamous *sneaky throw*: by casting through a type variable that erases to `RuntimeException`, you can throw a checked exception without declaring it, because the checked-ness is a compile-time fiction the compiler can't verify through the unchecked cast: ```java @SuppressWarnings("unchecked") static <T extends Throwable> void sneakyThrow(Throwable t) throws T { throw (T) t; // erased cast; at runtime there is no check } // caller can do: this.<RuntimeException>sneakyThrow(new IOException()); ``` The caller never declared `IOException`, yet it propagates at runtime — a direct side effect of throwing being erasure-safe while catching is not. ## Practical takeaways - You cannot catch by a type variable; catch a concrete supertype instead (e.g. `catch (Exception e)`) and branch with `instanceof`/`getClass()` if needed. - You *can* declare and rethrow type-variable exceptions. - The whole behavior falls out of one fact: catching is a runtime type test and `T` is erased.
- If you can't catch T, how do you handle a type-variable exception generically?Catch a concrete supertype the bound guarantees (e.g. `catch (Exception e)` for `T extends Exception`), then use instanceof or reflection to branch; or declare `throws T` and let the caller handle it.
- What is 'sneaky throw' and how does this restriction relate to it?Sneaky throw casts a Throwable through an unchecked type variable that erases to RuntimeException, letting a checked exception propagate without being declared. It works precisely because throwing a type variable is erasure-safe while catching it is forbidden.
saying these in an interview costs you the question
- Saying you can catch T as long as it extends Throwable — you cannot.
- Believing throwing a type variable is also forbidden — only catching is.
- Thinking the error is a runtime ClassCastException rather than a compile error.
- Confusing 'catch the bound' (allowed: catch Exception) with 'catch T' (forbidden).