W3docs

Java Reflection Introduction

What reflection is in Java, when to use it, and an overview of the java.lang.reflect package.

Java Reflection Introduction

Reflection is the ability of a program to inspect and manipulate its own structure at runtime: to ask a class what fields, methods, and constructors it has, to read and write those fields, to call those methods, and to create new instances — all without naming the types at compile time. Where ordinary Java is static (the compiler knows every type you touch), reflection is dynamic (you discover types from strings, configuration, or whatever is loaded at runtime). This part of the book is a tour of java.lang.reflect; this chapter sets the scene.

What reflection lets you do

Normal code names the type it works with:

User u = new User("ada");
String name = u.getName();

Reflective code reaches the same result without writing User or getName as compile-time tokens — they're strings resolved at runtime:

Class<?> cls = Class.forName("com.example.User");
Object u = cls.getDeclaredConstructor(String.class).newInstance("ada");
Object name = cls.getMethod("getName").invoke(u);

The second form is far more verbose and far slower, and it discards compile-time type checking. You'd never write it for ordinary application logic. You reach for it precisely when the type isn't known until runtime.

When reflection earns its keep

Reflection is the engine behind frameworks, not everyday code. Typical uses:

  • Dependency injection (Spring, Guice, CDI): the container reads annotations and constructor signatures, then instantiates and wires beans it has never seen the source for.
  • Serialization (Jackson, Gson): a JSON library walks an object's fields to read or populate them without you writing per-class mapping code.
  • ORMs (Hibernate, JPA): map columns to fields by reflecting over an entity class.
  • Test runners (JUnit): find methods annotated @Test and invoke them.
  • Plugin systems: load a class named in a config file and call a known interface method on it.

The common thread: a general mechanism operating over arbitrary user types it was not compiled against. That is exactly the problem reflection solves.

The core types in java.lang.reflect

Reflection starts from a Class object (covered in the next chapter) and branches into a small family of classes, each describing one kind of member:

TypeRepresentsGot from Class via
Fielda fieldgetField / getDeclaredField(s)
Methoda methodgetMethod / getDeclaredMethod(s)
Constructor<T>a constructorgetConstructor / getDeclaredConstructor(s)
Parametera method/constructor parameterExecutable.getParameters()
Modifiera helper for the int modifier bitsetstatic methods

Field, Method, and Constructor share a common superclass hierarchy: Member (the interface) and AccessibleObject (which carries setAccessible). That shared base is why "make it accessible" and "read its annotations" look identical no matter which member you hold.

The get… vs getDeclared… split

Almost every lookup method comes in two flavours, and the distinction matters in every later chapter:

  • getField / getMethod / getConstructor — returns public members, including inherited ones from superclasses and interfaces.
  • getDeclaredField / getDeclaredMethod / getDeclaredConstructor — returns members of any access level (private, protected, package), but only those declared on this exact class — nothing inherited.

So getMethods() sees a public method inherited from a parent but not a private helper on the class itself; getDeclaredMethods() sees the private helper but not the inherited public method. To reach a private inherited member you walk up getSuperclass() calling getDeclared… at each level.

The cost: speed, safety, and encapsulation

Reflection is powerful, and each power has a price.

  • Performance. Reflective calls are slower than direct calls — method/field lookups, access checks, and argument boxing all add overhead. The JIT optimizes hot reflective calls well, but reflection in a tight loop is a smell. Cache the Method/Field objects; never look them up per call.
  • No compile-time safety. A typo in a method name compiles fine and fails at runtime with NoSuchMethodException. Refactoring tools rename getName everywhere — except inside your string "getName".
  • Breaking encapsulation. setAccessible(true) lets you read and write private state. That's how serializers populate fields with no setter, but it couples you to internals that the class's author never promised to keep stable.
  • Module restrictions. Since Java 9, the module system can deny reflective access to non-exported packages. Calling setAccessible(true) across a module boundary that hasn't opens the package throws InaccessibleObjectException.

A worked example: a tiny generic object dumper

To make the scope concrete, here's a single reflective routine that prints any object's fields and their values — the kind of thing a debugger or a logging library does. It names no application type; it works on whatever it's handed.

java— editable, runs on the server

What to take from the run:

  • The dump method named no concrete type and yet printed both Point and User. Its only contract is "give me an Object"; everything about the structure — field names, types, values — came from getClass() at runtime. That is the defining move of reflection: one routine, arbitrary inputs.
  • getDeclaredFields() returned all fields including the private ones, but reading them required setAccessible(true) first. Without that call, f.get(obj) on a private field throws IllegalAccessException. The lookup and the access are two separate gates.
  • Modifier.toString(f.getModifiers()) turned the raw modifier bitset into readable text like private final. Modifiers are stored as an int of flag bits; the Modifier helper decodes them so you don't test bits by hand.
  • The third object was built with no new User(...) anywhere in source — Class.forName("…$User") resolved the nested class from a string (note the $ separator for nested types), and getDeclaredConstructor(...).newInstance(...) constructed it. This is the plugin-loading pattern in miniature: a name in, an object out.
  • Reading the field value (f.get(obj)) and reading the field metadata (f.getName(), f.getModifiers()) are independent. Metadata needs no instance and no accessibility; values need the object and, for private fields, the accessibility flag.

How the rest of this part is organised

Each remaining chapter zooms into one corner:

  • Class objects — the three ways to get a Class<T> and what it tells you.
  • Fields — inspecting, reading, and writing fields (including private and final).
  • Methods — finding and invoking methods, overload resolution, return values.
  • Constructors — building instances reflectively, including private constructors.
  • Annotations — reading the metadata you attached in Part 16, at runtime.
  • Dynamic proxies — synthesising whole interface implementations at runtime.

Throughout, keep the trade-off in mind: reflection is the right tool when the type genuinely isn't known until runtime, and the wrong tool when it is. The next chapter starts at the root of it all — the Class object.

Practice

Practice

A logging library needs to print the field values of any object passed to its 'log(Object o)' method, including objects whose classes it was never compiled against and whose fields are 'private'. Which combination is the minimal correct approach?