Restrictions on Generics

1. Generic Types Cannot Use Primitive Types

Generics only work with reference types, not primitives.

Pair<int, char> p; // ERROR

Use wrapper classes:

Pair<Integer, Character> p = new Pair<>(8, 'a');

Java automatically converts 8Integer and 'a'Character using autoboxing.

int ❌ → Integer


2. Cannot Create an Object of a Type Parameter

You cannot do:

static <T> void create() {
    T obj = new T(); // ERROR
}

Java doesn't know what T actually is at runtime.

Instead, you can pass a class:

static <T> T create(Class<T> type) throws Exception {
    return type.getDeclaredConstructor().newInstance();
}

Use:

String s = create(String.class);

3. Cannot Use Type Parameters for Static Fields

This is not allowed:

class Box<T> {
    static T value; // ERROR
}

Why?

static belongs to the class, but T can be different for each object/type:

Box<String>
Box<Integer>

Java wouldn't know which T the static field should use.

Instance fields are fine:

class Box<T> {
    T value; // OK
}

4. Cannot Use instanceof with Specific Generic Types

This is not allowed:

if (list instanceof List<String>) { // ERROR
}

Because generic type information is erased at runtime.

Java cannot distinguish:

List<String>
List<Integer>

You can use an unbounded wildcard:

if (list instanceof List<?>) { // OK
}

5. Cannot Create Arrays of Parameterized Types

This is not allowed:

List<String>[] lists = new List<String>[10]; // ERROR

Because arrays know their element type at runtime, but generic type arguments are erased.

You can create:

List<?>[] lists = new List<?>[10]; // OK

6. Generic Classes Cannot Extend Throwable

You cannot create a generic exception:

class MyException<T> extends Exception { } // ERROR

Java also doesn't allow:

catch (T e) { } // ERROR

However, a type parameter can appear in throws:

class Parser<T extends Exception> {

    void parse() throws T {
        // ...
    }
}

7. Cannot Overload Methods with the Same Erasure

This is not allowed:

void print(List<String> list) { }

void print(List<Integer> list) { } // ERROR

Why?

Because after type erasure, both become:

void print(List list)

So Java sees two methods with the same signature.

You can instead use different method names:

void printStrings(List<String> list) { }

void printIntegers(List<Integer> list) { }

Quick Revision

Restriction Example
No primitive type arguments List<int>
Can't create T directly new T()
No static field of type T static T value
No specific generic instanceof x instanceof List<String>
No generic arrays new List<String>[10]
No generic Throwable class E<T> extends Exception
No catch(T) catch(T e)
No same-erasure overloads print(List<String>) + print(List<Integer>)

Easy way to remember

Most of these restrictions exist because of type erasure:

Java knows generic types at compile time, but most generic type information is gone at runtime.