skip to content

When creating instances of a type parameter, when should you use a `Supplier<T>`/factory versus a `Class<T>` token?

level: juniorimportance: should knowfreq 35%

answer

  1. Both work around the new T() ban via delegation
  2. Supplier<T> = simple, compile-time safe, any constructor, no reflection
  3. Class<T> = need runtime class: cast/isInstance/Array.newInstance/map key
  4. Token = reflection -> runtime failures, no-arg ctor only
  5. Default to factory; token only when runtime class is needed

basics

~20 s

Use a Supplier<T> (or factory) when you just need to make new objects — it's simple, checked by the compiler, works with any constructor, and uses no reflection. Use a Class<T> token when you also need the actual class for other reasons, like reflection, casting, or building a real typed array.

solid answer

~50 s

Both solve the same root problem: erasure means you can't `new T()`, so creation must be delegated. A `Supplier<T>` (e.g. `ArrayList::new`) or a small factory interface is the default choice: it's fully compile-time type-safe, requires no reflection, and naturally handles constructors that take arguments (`() -> new Point(1,2)`). A `Class<T>` token (e.g. `Point.class`) is the right tool when you need the runtime `Class` object for more than construction — reflective instantiation alongside `type.cast(...)`, `type.isInstance(...)`, `Array.newInstance(type, n)`, or using the class as a map key in a typesafe heterogeneous container. Its downsides: it relies on reflection (so it needs an accessible no-arg constructor and throws reflective exceptions at runtime), and it can't express constructor arguments cleanly. Rule of thumb: prefer the factory; reach for the token only when the runtime class itself is part of what you need.

code

java · 9 lines
java
// Factory: simple, type-safe, supports constructor args
static <T> T viaFactory(java.util.function.Supplier<T> f) { return f.get(); }
Point p = viaFactory(() -> new Point(1, 2));

// Token: when you ALSO need the runtime class
static <T> T viaToken(Class<T> type) throws ReflectiveOperationException {
    T obj = type.getDeclaredConstructor().newInstance();
    return type.cast(obj); // checked cast available because we have the Class
}

go deeper

for a junior

Understands you must pass in either a factory or a Class so generic code can make a T, and that a Supplier is the easy default.

for a middle

Can write both a Supplier-based and a Class-token-based factory method and explain that the token uses reflection while the supplier doesn't.

for a senior

Articulates the full capability difference (cast/isInstance/Array.newInstance/map key) and chooses correctly per use case, noting reflection costs and constructor-argument handling.

for a principal

Designs APIs that expose the right injection point (factory vs token vs both), weighing reflective failure modes, framework conventions, and ergonomics for callers.

## Same problem, two tools Because Java **erases** generic type arguments, code parameterized by `T` cannot write `new T()` — at runtime there's no class or constructor for `T`. So whenever generic code needs to *create* a `T`, it must get the creation capability from outside. The two standard ways to inject that capability are a **factory** (`Supplier<T>` or a custom interface) and a **type token** (`Class<T>`). Choosing between them is a recurring design decision. ## Option A: `Supplier<T>` / factory A `Supplier<T>` is a functional interface with one method, `T get()`. The caller supplies how to build the object: ```java static <T> List<T> filled(int n, Supplier<T> factory) { List<T> list = new ArrayList<>(); for (int i = 0; i < n; i++) list.add(factory.get()); return list; } // calls: filled(3, ArrayList::new); // no-arg constructor reference filled(3, () -> new Point(0, 0)); // constructor WITH arguments ``` Strengths: - **Compile-time type-safe** — the compiler verifies the supplier returns a `T`. - **No reflection** — no `ReflectiveOperationException`, no access/module issues. - **Any constructor** — a lambda can call constructors that take arguments, or even return a cached/pooled object. Weakness: it provides *only* construction. If you also need the runtime class for casting, instance tests, or array creation, a supplier can't give you that. ## Option B: `Class<T>` token The caller passes the runtime class object: ```java static <T> T make(Class<T> type) throws ReflectiveOperationException { return type.getDeclaredConstructor().newInstance(); } make(StringBuilder.class); ``` Strengths: - The token *is* the runtime type, so beyond construction you also get `type.cast(x)` (checked cast), `type.isInstance(x)` (erasure-safe `instanceof`), `Array.newInstance(type, n)` (real typed array), and a usable map key for typesafe heterogeneous containers. Weaknesses: - **Reflection-based:** requires an accessible no-arg constructor (`Class.newInstance()` itself is deprecated since Java 9 — use `getDeclaredConstructor().newInstance()`), and failures show up at runtime, not compile time. - **No clean way to pass constructor arguments.** - Can't capture a full generic type (no `List<String>.class`). ## Decision rule - **Default to the factory/`Supplier<T>`** when all you need is to create objects — it's simpler and safer. - **Use the `Class<T>` token** when the *runtime class itself* is needed: reflective work, checked casting, instance checks, typed-array creation, or class-keyed lookups. Frameworks (Jackson, Spring, JUnit) often take tokens precisely because they do this kind of reflective work. - You can also combine: take a `Class<T>` when you must, but expose a `Supplier<T>`-based overload for the common construction-only case. ## Key takeaway Both dodge the `new T()` ban by delegating creation. A `Supplier<T>` is the simpler, compile-time-safe default that handles constructor arguments; a `Class<T>` token is for when you genuinely need the runtime class for reflection, casting, instance tests, or typed arrays.

  • Can a Supplier<T> create objects whose constructor needs arguments?
    Yes — a lambda like () -> new Point(1, 2) captures the arguments, so the supplier returns a fully-built object. The Class<T> reflective newInstance() path only calls a no-arg constructor easily.
  • Give a case where only the Class<T> token works, not a Supplier.
    When you need to do type.cast(x), type.isInstance(x), build a genuinely typed array with Array.newInstance(type, n), or use the type as a key in a Map<Class<?>, Object> — a Supplier provides construction only, not the runtime class.

saying these in an interview costs you the question

  • Always reaching for Class<T> when a Supplier would do
  • Claiming Supplier<T> can't handle constructor arguments
  • Saying Class<T> construction is compile-time type-safe (it's reflective/runtime)
  • Forgetting Class.newInstance() is deprecated

context