Java · lesson 65 of 66
Beginner
17- 1 Introduction to Java
- 2 Setting Up Java
- 3 Data Types in Java
- 4 Operators in Java
- 5 Variables and Operators in Java
- 6 Control Flow in Java
- 7 Arrays in Java
- 8 Functions (Methods) in Java
- 9 Strings in Java
- 10 Methods in Java
- 11 Java OOP — Complete Guide
- 12 Introduction to Object-Oriented Programming in Java
- 13 Encapsulation in Java
- 14 Inheritance in Java
- 15 Polymorphism in Java
- 17 Abstraction in Java
- 18 Error Types in Java
Intermediate
38- 16 Regular Expressions in Java
- 19 Interfaces in Java — A Deep Dive
- 20 Abstract Classes in Java
- 21 Method Overloading in Java
- 22 Composition vs Inheritance in Java
- 23 Generics in Java
- 27 Exception Handling in Java
- 28 Java Streams API
- 29 Collections Framework in Java
- 30 Optional in Java
- 31 File Handling in Java
- 32 Records in Java
- 33 Java 8 Features
- 34 Sealed Classes and Interfaces in Java
- 35 Multithreading in Java
- 37 JDBC — Database Access in Java
- 39 Maven — Java Build and Dependency Management
- 42 Gradle — Modern Java Build Tool
- 44 Unit Testing with JUnit 5 and Mockito
- 45 Logging in Java with SLF4J and Logback
- 49 Spring Boot — Introduction
- 50 Dependency Injection in Spring Boot
- 51 Java Project Ideas
- 52 Building REST APIs with Spring Boot
- 53 Validation in Spring Boot
- 54 Spring Data JPA — Database Access
- 55 Exception Handling in Spring Boot
- 56 Spring Security — Authentication and Authorization
- 57 JWT Authentication in Spring Boot
- 58 Dockerizing Spring Boot Applications
- 59 Java Enums: Beyond Simple Constants
- 60 Inner and Anonymous Classes in Java
- 61 Java Generics: Bounded Types, Wildcards, and Generic Methods
- 62 Java Date/Time API: LocalDate, LocalDateTime, and Duration
- 63 Java NIO.2: Paths, Files, and WatchService
- 64 Java Annotations: Custom Types and Meta-Information
- 65 Java Reflection: Dynamic Type Inspection and Method Invocation
- 66 Java Localization: ResourceBundle and Locale Support
Advanced
11Java Reflection: Dynamic Type Inspection and Method Invocation
Learn the Java Reflection API by running it: get Class objects, list members, invoke methods, read private fields, and hit the module-system wall.
By SkillByExample · Updated
Reflection lets a program inspect and use classes it did not know about at compile time: list
their members, call methods by name, and read fields — including private ones. Every example is a
single file run with the JDK 21 source launcher (java File.java).
Getting a Class object
public class ClassObjectsDemo {
public static void main(String[] args) throws Exception {
Class<?> c1 = "hello".getClass(); // from an instance
Class<?> c2 = String.class; // from a class literal
Class<?> c3 = Class.forName("java.lang.String"); // from a name at runtime
System.out.println(c1 == c2);
System.out.println(c2 == c3);
System.out.println(c1.getName() + " / " + c1.getSimpleName());
Class.forName("com.example.Missing");
}
}
$ java ClassObjectsDemo.java
true
true
java.lang.String / String
Exception in thread "main" java.lang.ClassNotFoundException: com.example.Missing
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
at jdk.compiler/com.sun.tools.javac.launcher.Main$MemoryClassLoader.loadClass(Main.java:662)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:423)
at java.base/java.lang.Class.forName(Class.java:414)
at ClassObjectsDemo.main(ClassObjectsDemo.java:10)
All three routes return the same Class object — a class loader holds one per type, so ==
works. Only Class.forName can fail at runtime, because the name is just a string: it throws
ClassNotFoundException when no such class is on the class path.
Listing fields, methods and constructors
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.Comparator;
public class InspectDemo {
static class Account {
private final String owner;
private long balance;
public Account(String owner) { this.owner = owner; }
public void deposit(long amount) { balance += amount; }
public long balance() { return balance; }
private void audit() {}
}
public static void main(String[] args) {
Class<?> c = Account.class;
for (Field f : c.getDeclaredFields())
System.out.println("field " + Modifier.toString(f.getModifiers()) + " " + f.getType().getSimpleName() + " " + f.getName());
Arrays.stream(c.getDeclaredMethods())
.sorted(Comparator.comparing(Method::getName))
.forEach(m -> System.out.println("method " + Modifier.toString(m.getModifiers()) + " " + m.getName() + Arrays.toString(m.getParameterTypes())));
for (Constructor<?> k : c.getDeclaredConstructors())
System.out.println("ctor " + k);
}
}
$ java InspectDemo.java
field private final String owner
field private long balance
method private audit[]
method public balance[]
method public deposit[long]
ctor public InspectDemo$Account(java.lang.String)
getDeclared* returns members declared on this class at any access level, which is why the
private audit appears; getMethods() would instead return only public members, inherited ones
included (toString, hashCode, …). The JVM does not promise any order for these arrays, so the
example sorts methods by name before printing.
Invoking a method by name
import java.lang.reflect.Method;
public class InvokeDemo {
public static class Calculator {
public int add(int a, int b) { return a + b; }
public int multiply(int a, int b) { return a * b; }
public static int negate(int a) { return -a; }
}
public static void main(String[] args) throws Exception {
Calculator calc = new Calculator();
for (String name : new String[] {"add", "multiply"}) {
Method m = Calculator.class.getMethod(name, int.class, int.class);
Object result = m.invoke(calc, 3, 4);
System.out.println(name + "(3, 4) = " + result + " [" + result.getClass().getSimpleName() + "]");
}
Method negate = Calculator.class.getMethod("negate", int.class);
System.out.println("negate(5) = " + negate.invoke(null, 5));
}
}
$ java InvokeDemo.java
add(3, 4) = 7 [Integer]
multiply(3, 4) = 12 [Integer]
negate(5) = -5
invoke takes the target object first, then the arguments. Primitive arguments are boxed on the
way in and the int result comes back boxed as an Integer. For a static method the target is
ignored, so null is passed.
Reading and writing private members
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class PrivateAccessDemo {
static class Config {
private String host = "localhost";
private int port = 8080;
private String describe() { return host + ":" + port; }
}
public static void main(String[] args) throws Exception {
Config config = new Config();
Field host = Config.class.getDeclaredField("host");
host.setAccessible(true);
System.out.println("host = " + host.get(config));
host.set(config, "prod.example.com");
Field port = Config.class.getDeclaredField("port");
port.setAccessible(true);
port.setInt(config, 9090);
Method describe = Config.class.getDeclaredMethod("describe");
describe.setAccessible(true);
System.out.println("describe() = " + describe.invoke(config));
}
}
$ java PrivateAccessDemo.java
host = localhost
describe() = prod.example.com:9090
setAccessible(true) switches off the language’s access check for that one Field or Method
object, so private state can be read and changed from outside the class. That is how serializers
and test tools work — and why reflection-heavy code is fragile when a private field is renamed.
The module system says no
import java.lang.reflect.Field;
public class ModuleWallDemo {
public static void main(String[] args) throws Exception {
Field value = String.class.getDeclaredField("value");
System.out.println("found: " + value);
value.setAccessible(true);
System.out.println("never printed");
}
}
$ java ModuleWallDemo.java
found: private final byte[] java.lang.String.value
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field private final byte[] java.lang.String.value accessible: module java.base does not "opens java.lang" to unnamed module @7ff2a664
at java.base/java.lang.reflect.AccessibleObject.throwInaccessibleObjectException(AccessibleObject.java:391)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:367)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:315)
at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:183)
at java.base/java.lang.reflect.Field.setAccessible(Field.java:177)
at ModuleWallDemo.main(ModuleWallDemo.java:7)
Finding the private field works, but since Java 16 the JDK’s own packages are strongly
encapsulated: setAccessible on java.lang.String internals throws
InaccessibleObjectException, because module java.base does not open java.lang. The only way
past it is a deliberate opt-in on the command line, --add-opens java.base/java.lang=ALL-UNNAMED —
not something library code should need.
Exceptions arrive wrapped
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class WrappedDemo {
public static class Parser {
public int parse(String s) { return Integer.parseInt(s); }
}
public static void main(String[] args) throws Exception {
Method parse = Parser.class.getMethod("parse", String.class);
System.out.println(parse.invoke(new Parser(), "42"));
try {
parse.invoke(new Parser(), "forty-two");
} catch (InvocationTargetException e) {
System.out.println("wrapper: " + e.getClass().getSimpleName());
System.out.println("cause: " + e.getCause());
}
}
}
$ java WrappedDemo.java
42
wrapper: InvocationTargetException
cause: java.lang.NumberFormatException: For input string: "forty-two"
The NumberFormatException thrown inside parse does not escape as itself — invoke wraps it in
InvocationTargetException. Code that calls methods reflectively should unwrap getCause(), or
error messages end up pointing at the reflection layer instead of the real failure.
Arrays and enums
import java.lang.reflect.Array;
public class ArrayEnumDemo {
enum Day { MONDAY, TUESDAY, WEDNESDAY }
public static void main(String[] args) {
Object array = Array.newInstance(String.class, 3);
Array.set(array, 0, "first");
System.out.println(array.getClass().getSimpleName() + " length " + Array.getLength(array));
System.out.println("component: " + array.getClass().getComponentType().getSimpleName());
System.out.println("element 0: " + Array.get(array, 0));
Class<Day> day = Day.class;
System.out.println("isEnum: " + day.isEnum());
for (Day d : day.getEnumConstants())
System.out.println(d + " ordinal " + d.ordinal());
}
}
$ java ArrayEnumDemo.java
String[] length 3
component: String
element 0: first
isEnum: true
MONDAY ordinal 0
TUESDAY ordinal 1
WEDNESDAY ordinal 2
Array.newInstance builds a real String[] from a Class known only at runtime — what generic
code like List.toArray needs, since generics are erased. getEnumConstants returns the constants
in declaration order, which is how config and JSON libraries map a string back to an enum.
A note on cost: a reflective call boxes its arguments, checks access and is harder for the JIT to
inline. Look Method objects up once, cache them, and keep reflection out of hot loops — or use
java.lang.invoke.MethodHandle when you need fast repeated dynamic calls.
Practice
- Write
dumpFields(Object)that prints every declared field name and value of any object, including private ones.
Solution
import java.lang.reflect.Field;
public class DumpDemo {
static class Sample {
private String name = "test";
private int count = 42;
}
static void dumpFields(Object obj) throws IllegalAccessException {
for (Field f : obj.getClass().getDeclaredFields()) {
f.setAccessible(true);
System.out.println(f.getName() + " = " + f.get(obj));
}
}
public static void main(String[] args) throws Exception {
dumpFields(new Sample());
}
}
$ java DumpDemo.java
name = test
count = 42
- Create an object from a class name held in a
String, then call one of its methods by name.
Solution
import java.lang.reflect.Method;
public class CreateByName {
public static class Greeter {
public String greet(String name) { return "Hello, " + name; }
}
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("CreateByName$Greeter");
Object greeter = clazz.getDeclaredConstructor().newInstance();
Method greet = clazz.getMethod("greet", String.class);
System.out.println(greet.invoke(greeter, "Reflection"));
}
}
$ java CreateByName.java
Hello, Reflection
A nested class’s binary name uses $, so Class.forName needs CreateByName$Greeter, not
CreateByName.Greeter.
- Write a tiny test runner: run every public no-argument method whose name starts with
teston a class, printingPASSorFAILwith the unwrapped cause.
Solution
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
public class MiniRunner {
public static class MathTests {
public void testAdd() { if (1 + 1 != 2) throw new AssertionError("add"); }
public void testDivide() { int zero = 0; System.out.println(1 / zero); }
public void helper() {}
}
public static void main(String[] args) throws Exception {
Object suite = MathTests.class.getDeclaredConstructor().newInstance();
Method[] methods = MathTests.class.getMethods();
Arrays.sort(methods, Comparator.comparing(Method::getName));
for (Method m : methods) {
if (!m.getName().startsWith("test") || m.getParameterCount() != 0) continue;
try {
m.invoke(suite);
System.out.println("PASS " + m.getName());
} catch (InvocationTargetException e) {
System.out.println("FAIL " + m.getName() + ": " + e.getCause());
}
}
}
}
$ java MiniRunner.java
PASS testAdd
FAIL testDivide: java.lang.ArithmeticException: / by zero
This is, in miniature, what JUnit does — except JUnit finds tests by annotation rather than name.