Question
Given a generic Java class Foo<T>, how can a method inside Foo obtain the Class object for T? Calling T.class does not compile. What is the preferred way to handle this?
class Foo<T> {
void printType() {
// T.class; // Does not compile
}
}
Short Answer
Java generic type parameters are mostly erased at runtime, so T.class is not available. The usual and safest solution is to provide a Class<T> when constructing the generic object, then store and use that class token where needed.
Concept
Java implements generics with type erasure. The compiler uses generic information to check your code, but most type-parameter information is removed from ordinary objects at runtime.
For example, these two declarations have different compile-time types:
Foo<String> strings = new Foo<>();
Foo<Integer> numbers = new Foo<>();
At runtime, however, both are generally just instances of Foo. The runtime does not retain a general-purpose value that says the first object is Foo<String> and the second is Foo<Integer>.
That is why this is invalid:
class Foo<T> {
Class<T> type = T.class; // Compilation error
}
String.class works because String is a real, concrete runtime class. T is only a type parameter selected by the caller at compile time.
When your code genuinely needs runtime type information—for validation, reflection, object creation, or serialization—make it an explicit dependency by passing a Class<T> object into Foo.
Mental Model
Think of T as a blank label used while the compiler checks a form.
Foo<String>
means “this Foo works with strings” while compiling. Once the program runs, Java usually removes that label from the object. The object cannot look at itself and reliably discover what was written in the blank.
A Class<T> value is like keeping a copy of the label in the object:
new Foo<>(String.class)
Now Foo has an actual runtime object representing the type, so it can inspect it or pass it to APIs that require type information.
Syntax and Examples
Pass and store a Class<T> value, often called a class token.
class Foo<T> {
private final Class<T> type;
Foo(Class<T> type) {
this.type = type;
}
Class<T> getType() {
return type;
}
}
Create Foo with a concrete class literal:
Foo<String> textFoo = new Foo<>(String.class);
Foo<Integer> numberFoo = new Foo<>(Integer.class);
System.out.println(textFoo.getType().getName());
System.out.println(numberFoo.getType().getName());
Output:
java.lang.String
java.lang.Integer
The type Class<T> is important. It connects the runtime class token to the same generic type parameter used by Foo<T>. As a result, new Foo<String>(Integer.class) is rejected by the compiler.
A factory method can make construction clearer:
class <T> {
Class<T> type;
{
.type = type;
}
<T> Foo<T> {
<>(type);
}
}
Foo<String> foo = Foo.of(String.class);
Step by Step Execution
Consider this program:
class Foo<T> {
private final Class<T> type;
Foo(Class<T> type) {
this.type = type;
}
boolean accepts(Object value) {
return type.isInstance(value);
}
}
Foo<String> foo = new Foo<>(String.class);
System.out.println(foo.accepts("hello"));
System.out.println(foo.accepts(42));
Step by step:
String.classcreates aClass<String>object representing the runtimeStringclass.- The constructor receives that value and stores it in
type. foo.accepts("hello")callsString.class.isInstance("hello"), which returnstrue.foo.accepts(42)callsString.class.isInstance(42), which returnsfalse.- The program prints:
Real World Use Cases
A stored Class<T> is useful when an application needs a runtime description of a generic type.
- JSON or database mapping: A repository or serializer may need
User.classto map data into aUserobject. - Dependency injection: A container can use a class token as a lookup key, such as
container.get(Service.class). - Runtime validation: Validate that an incoming value matches the type expected by a generic component.
- Reflection: Inspect constructors, methods, fields, or annotations on a known class.
- Plugin registries: Register handlers by the event class they handle, such as
OrderCreated.class.
Example of a simple type-aware converter:
class Converter<T> {
private final Class<T> targetType;
Converter(Class<T> targetType) {
this.targetType = targetType;
}
Class<T> targetType() {
return targetType;
}
}
Converter<String> converter = new Converter<>(String.class);
Real Codebase Usage
In production code, prefer making runtime type requirements explicit instead of trying to recover erased generic information.
Constructor injection
Use this when every instance needs the type token:
final class Repository<T> {
private final Class<T> entityType;
Repository(Class<T> entityType) {
this.entityType = entityType;
}
}
Method parameter
Use this when only one operation needs the type:
static <T> T requireType(Object value, Class<T> type) {
if (!type.isInstance(value)) {
throw new IllegalArgumentException("Unexpected value type");
}
return type.cast(value);
}
type.cast(value) is preferable to (T) value when you already have a Class<T> token because it performs the runtime check through the supplied type.
Guard clauses
Validate required dependencies early:
this.type = java.util.Objects.requireNonNull(type, );
Common Mistakes
Trying to use T.class
This cannot compile:
class Foo<T> {
Class<T> type = T.class;
}
T is not a concrete runtime class. Pass Class<T> into the constructor instead.
Assuming getClass() returns T
class Foo<T> {
Class<?> type() {
return getClass();
}
}
This returns the class of the container object, such as Foo, not the type argument such as String.
Using the first element to discover a collection type
T first = values.get(0);
Class<?> type = first.getClass();
This fails for empty collections and can be misleading when subclasses are present. If the type is required, provide it explicitly.
Creating T with
Comparisons
| Need | Recommended tool | Example |
|---|---|---|
| Represent a concrete class at runtime | Class<T> | String.class |
| Verify or cast an unknown object | Class<T> methods | type.isInstance(value), type.cast(value) |
Create values of T | Supplier<T> or factory | new Foo<>(User::new) |
Represent List<String> completely | Type or a framework type token | Type describing |
Cheat Sheet
// Store a runtime class token
class Foo<T> {
private final Class<T> type;
Foo(Class<T> type) {
this.type = type;
}
}
Foo<String> strings = new Foo<>(String.class);
T.classis invalid in Java.- Java erases most generic type arguments at runtime.
- Pass
Class<T>when runtime class information is needed. - Use
type.isInstance(value)for safe runtime checks. - Use
type.cast(value)for a checked cast. getClass()returns the class of an object, not its generic argument.- Use
Supplier<T>when you need to createTvalues. Class<T>does not preserve nested parameter details such asList<String>.
FAQ
Why does T.class not compile in Java?
T is a generic type parameter, not a concrete class name. Java erases most generic type information at runtime, so it cannot create a class literal for T.
What is the preferred way to get the class for generic type T?
Pass a Class<T> value, such as String.class, to the constructor or to the method that needs it.
Can I use getClass() to find T?
No. getClass() returns the runtime class of the current object or value, not the generic argument used for a Foo<T> instance.
Is Class<T> type-safe?
Yes, in normal use. For example, new Foo<String>(Integer.class) does not compile because Integer.class is a Class<Integer>, not a Class<String>.
Can Class<T> represent ?
Mini Project
Description
Build a small runtime type validator. A generic wrapper receives an expected class and can determine whether unknown input is compatible with that type. This mirrors validation performed at API boundaries, configuration loaders, and plugin systems.
Goal
Create a reusable generic TypeValidator<T> that checks and safely casts values using a Class<T> token.
Requirements
- Create a generic
TypeValidator<T>class. - Accept and store a
Class<T>in its constructor. - Add a method that returns whether an
Objectmatches the expected type. - Add a method that returns a value as
Tor throws anIllegalArgumentExceptionfor an incompatible value. - Demonstrate the validator with
String.classand both valid and invalid inputs.
Keep learning
Related questions
Add External JAR Files to an IntelliJ IDEA Java Project
Learn how to add external JAR dependencies to an IntelliJ IDEA Java project using module libraries, and when to use Maven or Gradle instead.
Avoiding Java Code in JSP with JSP 2: EL and JSTL Explained
Learn how to avoid Java scriptlets in JSP 2 using Expression Language and JSTL, with examples, best practices, and common mistakes.
Call a Method After a Delay in Android Java
Learn how to run Java code after a delay in Android using Handler.postDelayed, manage the main thread, and cancel callbacks safely.