🪞 Reflection, Annotations & Modules · Advanced

The Class object in Java

getClass(), .class literals, Class.forName.

🧩 The mysterySpring reads a class name from a config file and creates that object, a class it had never heard of when it was compiled. The trick starts with one special object per type.

One ID card per type

Every loaded type has **exactly one Class object describing it. Three ways to get it: obj.getClass() (the object's runtime type), a class literal** like String.class (type known at compile time), or **Class.forName("java.util.ArrayList")** (look up by name at runtime).

Class<?> a = "hi".getClass();
Class<?> b = String.class;
Class<?> c = Class.forName("java.lang.String");
// a == b && b == c
🔮 Predict it

Runtime type wins

What does this print?

Object o = 42;
Number n = 3.5;
System.out.println(o.getClass().getSimpleName());
System.out.println(n.getClass().getSimpleName());
  1. Object Number
  2. Integer Double
  3. int double
Show the answer

getClass() reports the runtime class of the object, not the declared type of the variable. Autoboxing turned 42 into an Integer and 3.5 into a Double. Likewise, Animal a = new Dog() gives Dog.class.

Erasure: one Class per raw type

Generic type arguments are erased at runtime. List<String> and List<Integer> built with new ArrayList<>() share one Class object: ArrayList.class.

List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
a.getClass() == b.getClass(); // true
🔮 Predict it

Primitives and arrays

What does this print?

System.out.println(int.class.getName());
System.out.println(int[].class.getName());
var arr = String[].class;
System.out.println(arr.getSimpleName());
  1. int [I String[]
  2. int int[] String[]
  3. Integer [I [String
Show the answer

Even primitives have Class objects: int.class (the same object as Integer.TYPE). Array classes use JVM descriptor names in getName(), so int[] is **[I**, while getSimpleName() gives the friendly String[].

⚠️ The trap

Names are just strings

Class.forName takes a plain string, so the compiler can't check it. A typo compiles fine and fails only at runtime with the checked **ClassNotFoundException**.

void main() throws Exception {
    // typo: ArrayLst
    Class.forName("java.util.ArrayLst");
}   // ClassNotFoundException
💼 In the real world

Config-driven loading

A plugin system reads names like com.acme.CsvExporter from a config file. A class literal is impossible there: it needs the type at compile time. Class.forName(className) finds it at runtime. Reflection also uses primitive Class objects to describe parameters: getMethod("max", int.class, int.class).

Key takeaways

  1. obj.getClass() returns the runtime type
  2. Foo.class works for any type, even int.class
  3. Class.forName(name) loads and initializes by name
  4. Generics are erased: one Class per raw type

💡 A Class object is a type's ID card: one card per type, which anyone can ask to see at runtime.

🤯 Did you know?

int.class == Integer.TYPE is true, but int.class == Integer.class is false: the primitive int and the wrapper Integer have different Class objects.

Practice questions

What does this print?

Object o = "hello";
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
System.out.println(o.getClass().getSimpleName());
System.out.println(a.getClass() == b.getClass());
System.out.println(int[].class.getName());
  1. String false int[]
  2. String true [I
  3. String true int[]
  4. Object true [I
Check your answer

String true [I. getClass() reports the runtime type (String). Both lists are just ArrayList at runtime. Array classes use JVM descriptor names, so int[] is "[I".

What does this print?

void main() throws Exception {
    String name = "java.util.ArrayLst";
    Class<?> c = Class.forName(name);
    System.out.println(c.getSimpleName());
}
  1. Throws ClassNotFoundException
  2. null
  3. Compile error
  4. ArrayLst
Check your answer

Throws ClassNotFoundException. Class names passed to forName are just strings, so the typo compiles fine and fails at runtime with the checked ClassNotFoundException.

Holding a Class object is like holding the blueprint. Next: use it to read private fields and call methods by name. Reflection.