← Back to list

Java Reflection: A Beginner’s Guide to the API That Powers Everything

You’ve used Spring, Hibernate, and JUnit for years. This is the mechanism that makes all of them work — and it’s simpler than you think.

Wagner Franchin in ITNEXT · 2026-06-08 10:09 · 32 claps · 5.5 min read paywalled
#java #coding #software-development #software-engineering #careers
Open on Medium ↗
Wiki topics: 💻 · Programming

Java Reflection: A Beginner’s Guide to the API That Powers Everything

You’ve used Spring, Hibernate, and JUnit for years. This is the mechanism that makes all of them work — and it’s simpler than you think.

Image generated by Gemini

Image generated by Gemini

If you’ve ever written @Autowired, @Entity, or @Test, you've benefited from Java Reflection without knowing it. These annotations don't do anything on their own — a framework reads them at runtime using the Reflection API and acts accordingly.

This article explains what reflection is, how it works step by step, and when you should (and should not) use it yourself.

The core idea

At its simplest, reflection lets a Java program inspect and manipulate itself while it’s running.

Normally, your code is fixed at compile time. You call methods by name, access fields directly, and the compiler checks everything. Reflection breaks that contract — it lets you ask a class “what fields do you have?”, “what methods are you hiding?”, and then act on those answers dynamically, even bypassing private and final access modifiers.

The entry point is always the Class object. Every loaded class in the JVM has one, and you retrieve it in one of three ways:

// From an existing instance
Class<?> c1 = someObject.getClass();

// From the class literal (preferred when you know the type)
Class<?> c2 = Dog.class;

// From a fully qualified name (used by frameworks at runtime)
Class<?> c3 = Class.forName("com.example.Dog");

Once you have the Class object, the entire Reflection API opens up.

A concrete example: the Dog class

Throughout this guide we’ll work with a single example class that covers the common cases you’ll encounter:

public class Dog {

    private final String name;   // private AND final
    private int age;

    public Dog(String name, int age) {
        this.name = name;
        this.age  = age;
    }

    public String getName()       { return name; }
    public int    getAge()        { return age;  }
    public void   setAge(int age) { this.age = age; }

    public  void bark()           { System.out.println(name + " says woof"); }
    private void growl()           { System.out.println(name + " growls");     }

    public  static void breathe() { System.out.println("breathing...");  }
    private static void sleep()   { System.out.println("sleeping...");   }
}

This class has private fields, a final field, public and private instance methods, and public and private static methods. It covers every scenario the Reflection API can handle.

Step 1 — Inspecting fields

getDeclaredFields() returns every field declared in the class, regardless of its access modifier. This is different from getFields(), which returns only public fields including those inherited from superclasses.

Dog dog = new Dog("Buddy", 3);
Field[] fields = dog.getClass().getDeclaredFields();

for (Field field : fields) {
    System.out.println(field.getName() + " : " + field.getType().getSimpleName());
}

// Output:
// name : String
// age  : int

At this point we can see the fields, but we can’t read or write them yet. Attempting to call field.get(dog) on a private field throws IllegalAccessException.

Step 2 — Reading and modifying private fields

To access a private field, you must call **setAccessible(true)** first. This instructs the JVM to skip its normal access checks for that specific Field object.

Field nameField = dog.getClass().getDeclaredField("name");
nameField.setAccessible(true);

// Read the current value
String currentName = (String) nameField.get(dog);
System.out.println(currentName); // Buddy

// Write a new value — even though the field is final
nameField.set(dog, "John John");
System.out.println(dog.getName()); // John John

A few precise points worth noting:

  • getDeclaredField(String name) takes the exact field name as a string. Rename the field and this line throws NoSuchFieldException at runtime — no compile-time warning.
  • Modifying a final field via reflection is technically possible but produces undefined behaviour in some JVM implementations. Avoid it in production code.
  • setAccessible(true) is per-Field instance, not global. Other code holding a separate Field reference to the same field is unaffected.

Step 3 — Invoking methods

getDeclaredMethods() returns every method declared in the class. To call one, use method.invoke(target, args...).

Public instance method

Method bark = dog.getClass().getDeclaredMethod("bark");
bark.invoke(dog); // Buddy says woof

No setAccessible needed for public methods.

Private instance method

Method growl = dog.getClass().getDeclaredMethod("growl");
growl.setAccessible(true);
growl.invoke(dog); // Buddy growls

Static method

Static methods belong to the class, not an instance. Pass null as the target object:

Method breathe = Dog.class.getDeclaredMethod("breathe");
breathe.invoke(null); // breathing...

Method sleep = Dog.class.getDeclaredMethod("sleep");
sleep.setAccessible(true);
sleep.invoke(null);   // sleeping...

Step 4 — Handling exceptions

Every reflective operation declares checked exceptions. You must handle them explicitly — the compiler enforces this.

ExceptionWhen it occursNoSuchFieldExceptionThe field name passed to getDeclaredField() doesn't existNoSuchMethodExceptionThe method name or parameter types don't matchIllegalAccessExceptionYou called get, set, or invoke without calling setAccessible(true) firstInvocationTargetExceptionThe method was invoked successfully but threw an exception itself

A typical pattern wraps everything in a try-catch and preserves the original cause:

try {
    Field f = dog.getClass().getDeclaredField("name");
    f.setAccessible(true);
    f.set(dog, "Josh John");
} catch (NoSuchFieldException | IllegalAccessException e) {
    throw new RuntimeException("Reflection failed", e);
}

Why frameworks rely on this

Consider what Spring does when it starts up. It doesn’t have a list of your beans compiled in — it discovers them at runtime by:

  1. Scanning the classpath for classes annotated with @Component, @Service, @Repository, etc.
  2. Using Class.forName() to load each one.
  3. Calling getDeclaredFields() and getDeclaredMethods() to find injection points marked with @Autowired.
  4. Using setAccessible(true) and field.set(instance, dependency) to inject the dependency — even into private fields.

Every step in that process uses the exact API you’ve just learned. The annotation syntax is just a marker; reflection is the engine that reads the marker and acts on it.

The same applies to:

  • Hibernate — maps private fields to database columns without requiring getters.
  • JUnit — discovers and invokes test methods annotated with @Test.
  • Jackson — serialises and deserialises private fields to and from JSON.

What you should not do

Reflection is a sharp tool. These are the precise failure modes to watch for:

Silent breakage on rename. If you rename growl to growlSoftly, the string "growl" in your reflective call becomes wrong. No compile error. A NoSuchMethodException surfaces at runtime — possibly in production.

Performance cost. Reflective method calls bypass JIT optimisations and involve additional object allocations. In a tight loop this is measurable. Cache Field and Method objects rather than looking them up repeatedly if performance matters.

Encapsulation erosion. private is a contract: "this is an implementation detail, do not depend on it." Reflection breaks that contract. If the class owner changes the internal structure, your reflective code fails — and they had no way of knowing you were depending on it.

GraalVM native image. If you compile to a native binary with GraalVM, the reflective metadata is stripped at build time unless you explicitly register the classes. Frameworks like Quarkus solve this by processing annotations at build time instead.

When reflection is the right choice

Use reflection when you are:

  • Writing a framework or library that must work with user-defined classes it cannot reference at compile time.
  • Writing test utilities that need to reach private state to set up a specific scenario.
  • Building serialisation tools that need field-level access without requiring the class to implement an interface.

Do not use reflection in ordinary application logic. If you find yourself reaching for it there, the API you’re working against probably needs to be redesigned.

Summary

  • Class object: Obtain it via instance.getClass(), ClassName.class, or Class.forName("...")
  • Field access: Use getDeclaredFields() to list fields, setAccessible(true) to bypass private, then get()/set() to read/write values
  • Method invocation: Use getDeclaredMethod() to look up a method by name, then invoke(target, args...) to call it (pass null for static methods)
  • Exception handling: Handle NoSuchFieldException, NoSuchMethodException, IllegalAccessException, and InvocationTargetException

Reflection is not magic — it’s a well-defined API that exposes the JVM’s internal model of your classes. Understanding it transforms the “how does Spring actually work?” question from a mystery into a straightforward answer. And that understanding makes you a sharper Java developer.

Futher Readings

[embed]Java Interview Riddles: 6 Deceptive Questions That Catch Even Senior Engineers Off Guard Can you solve these 6 trick Java coding questions? Test your knowledge of JVM string pools, autoboxing, and float…itnext.io

📣 Call to Action

If you are interested in following along with my journey, I invite you to dive into all the details provided below:

[embed]Wagner Franchin - Medium Read writing from Wagner Franchin on Medium. Software Engineer writing about tech, coding, career and more.medium.com

Thanks for reading


메타데이터
post_id
65160866ea05
slug
java-reflection-a-beginners-guide-to-the-api-that-powers-everything-65160866ea05
url
https://itnext.io/java-reflection-a-beginners-guide-to-the-api-that-powers-everything-65160866ea05
canonical_url
https://itnext.io/java-reflection-a-beginners-guide-to-the-api-that-powers-everything-65160866ea05
author_url
https://medium.com/@wagnerjfr
status
ok
fetched_at
2026-06-10 08:17:25