skip to content

Why can't you write `new T()` inside a generic class or method in Java, and what is the standard workaround?

level: middleimportance: must knowfreq 70%

answer

  1. Erasure: T becomes Object/bound at runtime
  2. No runtime class -> no constructor to call
  3. Class<T> token + getDeclaredConstructor().newInstance()
  4. Supplier<T> / factory (preferred, type-safe, no reflection)
  5. Class.newInstance() deprecated since Java 9

basics

~20 s

Java erases the generic type T at compile time, so at runtime the program doesn't know which class T is and can't call its constructor. The usual fix is to pass a Class<T> object and call clazz.getDeclaredConstructor().newInstance(), or pass a factory/supplier that creates the object for you.

solid answer

~40 s

Generics in Java are implemented with type erasure: the type parameter T exists only at compile time and is replaced by its bound (usually Object) in the bytecode. So `new T()` is impossible because at runtime there is no information about which concrete class T stands for, and the JVM wouldn't know which constructor to invoke. The two standard workarounds are: (1) pass a `Class<T>` type token and create instances reflectively via `clazz.getDeclaredConstructor().newInstance()`; or (2) pass a factory abstraction such as `Supplier<T>` or a custom factory interface, letting the caller decide how to build the object. The factory/Supplier approach is generally preferred today because it is type-safe, avoids reflection, handles constructors with arguments, and surfaces failures at compile time rather than as runtime reflective exceptions.

code

java · 17 lines
java
// Won't compile:
class Box<T> {
    // T value = new T();  // error: type parameter T cannot be instantiated directly
}

// Workaround A: Class<T> type token + reflection
static <T> T makeViaToken(Class<T> type) throws ReflectiveOperationException {
    return type.getDeclaredConstructor().newInstance();
}

// Workaround B: Supplier<T> factory (preferred)
static <T> T makeViaFactory(java.util.function.Supplier<T> factory) {
    return factory.get();
}

String a = makeViaFactory(String::new);          // ""
ArrayList<Integer> b = makeViaFactory(ArrayList::new);

go deeper

for a junior

Knows new T() doesn't compile and that you can pass a factory/Supplier or a Class object instead, even if the erasure mechanics are fuzzy.

for a middle

Explains type erasure clearly, knows both the Class<T> token and Supplier<T> workarounds, and can write the reflective newInstance call.

for a senior

Articulates why Supplier is preferred (compile-time safety, no reflection, arg constructors), knows erasure goes to the bound not always Object, and notes Class.newInstance() deprecation.

for a principal

Frames it against reified generics in other languages, the backward-compatibility rationale for erasure, and API-design implications of choosing token-vs-factory in a framework.

## The problem A **generic class** or **generic method** is parameterized by a *type parameter* — a placeholder like `T` that stands for some real type the caller chooses, e.g. `class Box<T>` or `<T> T make()`. You might naturally want to create a fresh `T` inside such code: ```java class Box<T> { T value = new T(); // DOES NOT COMPILE } ``` The compiler rejects this. To understand why, you need **type erasure**. ## What type erasure is Java added generics in Java 5 but had to stay backward-compatible with pre-generics code and with the existing JVM. The chosen implementation is *erasure*: generics are a **compile-time-only** feature. The compiler checks your types, then **erases** the type parameters from the bytecode. Every `T` is replaced by its *bound* — `Object` if `T` is unbounded, or the leftmost bound if you wrote `<T extends Number>` (then `T` becomes `Number`). The compiler inserts hidden casts where needed so the code still behaves correctly. The consequence: **at runtime, a `Box<String>` and a `Box<Integer>` are the exact same class `Box`** — there is no stored memory of what `T` was. The type argument is simply *not present* at runtime. This is called the type parameter being *non-reifiable* (a *reifiable* type is one whose full type information survives to runtime; generic type arguments are not reifiable). ## Why `new T()` fails To execute `new SomeClass()`, the JVM must (a) know the exact class to allocate and (b) know which constructor to call. With `new T()`, after erasure there is no class named `T` and no way to recover the concrete type the caller picked. The compiler also can't even prove that `T` *has* a no-argument constructor. So `new T()` is forbidden outright — it is a compile error, not a runtime surprise. ## Workaround 1: pass a `Class<T>` type token The caller hands you a *type token* — a `Class<T>` object that *does* carry the concrete class at runtime — and you instantiate reflectively: ```java static <T> T make(Class<T> type) throws ReflectiveOperationException { return type.getDeclaredConstructor().newInstance(); } // call: String s = make(String.class); ``` Downsides: it only works if `T` has an accessible matching constructor; it throws *reflective* exceptions at runtime instead of giving compile-time safety; and constructors that need arguments are awkward. (Note: `Class.newInstance()` is deprecated since Java 9 — use `getDeclaredConstructor().newInstance()`.) ## Workaround 2: pass a factory / `Supplier<T>` (preferred) Let the caller supply the construction logic: ```java static <T> T make(Supplier<T> factory) { return factory.get(); } // call: StringBuilder sb = make(StringBuilder::new); Point p = make(() -> new Point(1, 2)); // constructor with args, no problem ``` This is **type-safe at compile time**, needs no reflection, naturally supports constructor arguments, and fails fast if the type can't be built. For these reasons it is the modern idiom; the `Class<T>` token is mostly used when you also need the class for other reflective reasons (e.g. frameworks). ## Key takeaway You cannot directly instantiate a type parameter because erasure removes the type at runtime. Move the *act of creation* to something that does know the type: a `Class<T>` token (reflection) or, better, a `Supplier<T>`/factory provided by the caller.

  • Why is Supplier<T> usually preferred over passing a Class<T>?
    It's checked at compile time, needs no reflection, supports constructors with arguments, and surfaces errors as compile errors rather than runtime ReflectiveOperationExceptions.
  • What does T erase to if you declare <T extends Number>?
    It erases to Number (the leftmost/only bound), not Object; the compiler inserts casts back to the used type as needed.

saying these in an interview costs you the question

  • Claiming Java generics are reified like C# generics
  • Saying new T() compiles but throws at runtime (it's a compile error)
  • Recommending Class.newInstance() without noting it's deprecated
  • Forgetting that T might not have a no-arg constructor

context