Generic Restrictions
Generic Restrictions
Generics make Java code reusable and type-safe, but they come with a set of hard restrictions that trip up even experienced developers. Understanding why these restrictions exist — rooted in the JVM feature called type erasure — makes them much easier to remember and work around.
Quick recap: type erasure
At compile time the Java compiler checks all generic types. When it produces bytecode it erases the type parameters, replacing them with either Object or the first bound. At runtime a List<String> and a List<Integer> are both just List — the JVM sees no difference. This erasure is the root cause of almost every generic restriction below.
Restriction 1 — No primitive type arguments
Type parameters must be reference types. You cannot use int, double, boolean, or any other primitive as a type argument.
Why? After erasure the slot in the list holds an Object reference. Primitives are not objects; they cannot be stored as Object. Wrapper types (Integer, Double, Long, etc.) are full objects, so they work fine. Java's autoboxing converts back and forth transparently in most situations.
Restriction 2 — Cannot create arrays of parameterised types
You cannot create an array whose component type is a parameterised generic type.
Why? Java arrays are covariant: a String[] is a Object[]. Arrays also carry their component type at runtime and do an assignability check on every write (array store check). Generics are erased, so List<String>[] and List<Integer>[] both become List[] at runtime. Combining covariant arrays with erased types would allow you to silently store the wrong type — the compiler blocks this to prevent a heap pollution bug that would only surface later as a mysterious ClassCastException.
@SuppressWarnings("unchecked") annotation is your acknowledgement that you have checked the code manually. Prefer List<List<String>> to avoid the problem entirely.
Restriction 3 — Cannot use instanceof with parameterised types
Because type parameters are erased, the JVM has no information about them at runtime. Therefore instanceof with a parameterised type is illegal.
The wildcard form List<?> is allowed because it makes no claim about the type argument — it just says "some List". The parameterised form List<String> would promise a runtime check that the JVM cannot perform after erasure.
Restriction 4 — Cannot instantiate a type parameter directly
Writing new T() inside a generic class is illegal because after erasure the compiler does not know which constructor to call.
Restriction 5 — Static members cannot use class type parameters
Static fields and static methods belong to the class itself, not to any particular parameterised instance. Using the class-level type parameter in a static context is therefore illegal.
Putting it all together — a clean generic utility class
Summary
- No primitives as type arguments — use wrapper types; autoboxing handles the conversion.
- No
new T[]— use aList<T>or pass a raw array with an unchecked cast. - No
instanceof List<String>— check againstList<?>(the raw or wildcard form). - No
new T()— pass aSupplier<T>orClass<T>instead. - No static use of the class type parameter — give the static method its own type parameter.
Every one of these restrictions traces back to the same cause: type erasure. Once you internalise that the JVM sees only raw types at runtime, the rules stop feeling arbitrary and start making perfect sense.