
Java Reflection Example Tutorial – Inspect and Modify Classes at Runtime
Java Reflection is one of those language features that feels like magic until you understand what’s happening under the hood. It allows your Java programs to inspect classes, interfaces, fields, and methods at runtime without knowing their names at compile time. Think of it as a way to make your code self-aware – you can examine class structures, modify private fields, invoke methods dynamically, and even create instances of classes on the fly. While reflection is incredibly powerful and forms the backbone of many frameworks like Spring and Hibernate, it comes with performance costs and can break encapsulation if not used carefully. This tutorial will walk you through practical reflection examples, show you how to avoid common pitfalls, and demonstrate real-world scenarios where reflection shines.
How Java Reflection Works
Reflection operates through the java.lang.reflect
package, which provides classes like Class
, Method
, Field
, and Constructor
. When the JVM loads a class, it creates a Class
object that contains metadata about that class. This metadata includes information about fields, methods, constructors, annotations, and the class hierarchy.
The entry point is usually the Class
object, which you can obtain in several ways:
// Method 1: Using .class literal
Class<String> clazz1 = String.class;
// Method 2: Using getClass() on an instance
String str = "Hello";
Class<?> clazz2 = str.getClass();
// Method 3: Using Class.forName() with fully qualified name
Class<?> clazz3 = Class.forName("java.lang.String");
Once you have a Class
object, you can access its members through various methods. The reflection API distinguishes between declared members (those declared in the class itself) and all members (including inherited ones).
Step-by-Step Implementation Guide
Let’s start with a sample class that we’ll use throughout our examples:
public class Employee {
private String name;
private int age;
private double salary;
public String department;
public Employee() {}
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
private Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private void calculateBonus() {
System.out.println("Calculating bonus for " + name);
}
public static void printCompanyInfo() {
System.out.println("Company: TechCorp Inc.");
}
}
Inspecting Class Information
public class ReflectionBasics {
public static void main(String[] args) {
Class<Employee> employeeClass = Employee.class;
// Get class name
System.out.println("Class name: " + employeeClass.getName());
System.out.println("Simple name: " + employeeClass.getSimpleName());
System.out.println("Package: " + employeeClass.getPackage().getName());
// Get superclass
System.out.println("Superclass: " + employeeClass.getSuperclass().getName());
// Check if it's an interface, enum, etc.
System.out.println("Is interface: " + employeeClass.isInterface());
System.out.println("Is enum: " + employeeClass.isEnum());
}
}
Working with Fields
public class FieldReflection {
public static void main(String[] args) throws Exception {
Employee emp = new Employee("John Doe", 30);
Class<?> clazz = emp.getClass();
// Get all declared fields (including private)
Field[] declaredFields = clazz.getDeclaredFields();
System.out.println("Declared fields:");
for (Field field : declaredFields) {
System.out.println(" " + field.getName() + " - " + field.getType().getSimpleName());
}
// Access and modify a private field
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // Bypass private access
// Get current value
String currentName = (String) nameField.get(emp);
System.out.println("Current name: " + currentName);
// Set new value
nameField.set(emp, "Jane Smith");
System.out.println("New name: " + emp.getName());
// Work with public fields
Field deptField = clazz.getDeclaredField("department");
deptField.set(emp, "Engineering");
System.out.println("Department: " + emp.department);
}
}
Method Invocation
public class MethodReflection {
public static void main(String[] args) throws Exception {
Employee emp = new Employee("Alice Johnson", 28);
Class<?> clazz = emp.getClass();
// Get and invoke public method
Method setNameMethod = clazz.getMethod("setName", String.class);
setNameMethod.invoke(emp, "Alice Brown");
System.out.println("Name after method call: " + emp.getName());
// Get and invoke private method
Method bonusMethod = clazz.getDeclaredMethod("calculateBonus");
bonusMethod.setAccessible(true);
bonusMethod.invoke(emp);
// Invoke static method
Method staticMethod = clazz.getMethod("printCompanyInfo");
staticMethod.invoke(null); // null for static methods
// Get method information
Method[] methods = clazz.getDeclaredMethods();
System.out.println("\nAll methods:");
for (Method method : methods) {
System.out.println(" " + method.getName() +
" - Parameters: " + method.getParameterCount() +
" - Return type: " + method.getReturnType().getSimpleName());
}
}
}
Constructor Manipulation
public class ConstructorReflection {
public static void main(String[] args) throws Exception {
Class<Employee> clazz = Employee.class;
// Get all constructors
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
System.out.println("Available constructors:");
for (Constructor<?> constructor : constructors) {
System.out.println(" Parameters: " + constructor.getParameterCount());
}
// Use public constructor
Constructor<Employee> publicConstructor = clazz.getConstructor(String.class, int.class);
Employee emp1 = publicConstructor.newInstance("Bob Wilson", 35);
System.out.println("Created employee: " + emp1.getName());
// Use private constructor
Constructor<Employee> privateConstructor = clazz.getDeclaredConstructor(
String.class, int.class, double.class);
privateConstructor.setAccessible(true);
Employee emp2 = privateConstructor.newInstance("Carol Davis", 32, 75000.0);
// Access the salary field to verify
Field salaryField = clazz.getDeclaredField("salary");
salaryField.setAccessible(true);
double salary = (double) salaryField.get(emp2);
System.out.println("Employee salary: " + salary);
}
}
Real-World Examples and Use Cases
JSON Serialization Framework
Here’s a simplified JSON serializer that uses reflection to convert objects to JSON:
import java.lang.reflect.Field;
import java.util.StringJoiner;
public class SimpleJsonSerializer {
public static String toJson(Object obj) throws Exception {
if (obj == null) return "null";
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
StringJoiner json = new StringJoiner(", ", "{", "}");
for (Field field : fields) {
field.setAccessible(true);
Object value = field.get(obj);
String jsonValue;
if (value == null) {
jsonValue = "null";
} else if (value instanceof String) {
jsonValue = "\"" + value + "\"";
} else {
jsonValue = value.toString();
}
json.add("\"" + field.getName() + "\": " + jsonValue);
}
return json.toString();
}
public static void main(String[] args) throws Exception {
Employee emp = new Employee("Mike Johnson", 29);
Field salaryField = Employee.class.getDeclaredField("salary");
salaryField.setAccessible(true);
salaryField.set(emp, 65000.0);
System.out.println(toJson(emp));
// Output: {"name": "Mike Johnson", "age": 29, "salary": 65000.0, "department": null}
}
}
Dynamic Configuration Injector
This example shows how to create a configuration system that injects values into annotated fields:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface ConfigValue {
String value();
}
class DatabaseConfig {
@ConfigValue("db.host")
private String host;
@ConfigValue("db.port")
private int port;
@ConfigValue("db.timeout")
private long timeout;
// Getters
public String getHost() { return host; }
public int getPort() { return port; }
public long getTimeout() { return timeout; }
}
public class ConfigInjector {
private static Map<String, String> properties = new HashMap<>();
static {
properties.put("db.host", "localhost");
properties.put("db.port", "5432");
properties.put("db.timeout", "30000");
}
public static void inject(Object obj) throws Exception {
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
ConfigValue annotation = field.getAnnotation(ConfigValue.class);
if (annotation != null) {
String configKey = annotation.value();
String configValue = properties.get(configKey);
if (configValue != null) {
field.setAccessible(true);
// Convert string to appropriate type
Object convertedValue = convertValue(configValue, field.getType());
field.set(obj, convertedValue);
}
}
}
}
private static Object convertValue(String value, Class<?> targetType) {
if (targetType == String.class) {
return value;
} else if (targetType == int.class || targetType == Integer.class) {
return Integer.parseInt(value);
} else if (targetType == long.class || targetType == Long.class) {
return Long.parseLong(value);
}
// Add more type conversions as needed
return value;
}
public static void main(String[] args) throws Exception {
DatabaseConfig config = new DatabaseConfig();
inject(config);
System.out.println("Host: " + config.getHost());
System.out.println("Port: " + config.getPort());
System.out.println("Timeout: " + config.getTimeout());
}
}
Performance Considerations and Comparisons
Reflection has significant performance overhead compared to direct method calls. Here’s a benchmark comparison:
Operation | Direct Call (ns) | Reflection Call (ns) | Overhead Multiplier |
---|---|---|---|
Method Invocation | 2 | 150 | 75x |
Field Access | 1 | 80 | 80x |
Object Creation | 10 | 200 | 20x |
Performance optimization strategies:
- Cache
Method
,Field
, andConstructor
objects instead of looking them up repeatedly - Use
setAccessible(true)
once during initialization, not on every access - Consider using
MethodHandle
API for better performance in Java 7+ - Avoid reflection in tight loops or performance-critical paths
public class ReflectionCache {
private static final Map<String, Method> methodCache = new ConcurrentHashMap<>();
private static final Map<String, Field> fieldCache = new ConcurrentHashMap<>();
public static Method getCachedMethod(Class<?> clazz, String methodName, Class<?>... paramTypes)
throws NoSuchMethodException {
String key = clazz.getName() + "." + methodName + "." + Arrays.toString(paramTypes);
return methodCache.computeIfAbsent(key, k -> {
try {
Method method = clazz.getDeclaredMethod(methodName, paramTypes);
method.setAccessible(true);
return method;
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
});
}
}
Best Practices and Common Pitfalls
Security Considerations
Reflection can bypass access controls, which raises security concerns:
- Use
SecurityManager
to restrict reflection operations in production environments - Validate input when using
Class.forName()
to prevent class loading attacks - Be careful with
setAccessible(true)
as it can expose sensitive data - Consider using Java modules (Java 9+) to restrict reflection access
// Safe class loading with validation
public static Class<?> loadClassSafely(String className, Set<String> allowedPackages)
throws ClassNotFoundException {
boolean isAllowed = allowedPackages.stream()
.anyMatch(pkg -> className.startsWith(pkg));
if (!isAllowed) {
throw new SecurityException("Class not in allowed packages: " + className);
}
return Class.forName(className);
}
Exception Handling
Reflection operations throw various checked exceptions that need proper handling:
public class ReflectionExceptionHandling {
public static void safeFieldAccess(Object obj, String fieldName, Object value) {
try {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
} catch (NoSuchFieldException e) {
System.err.println("Field not found: " + fieldName);
} catch (IllegalAccessException e) {
System.err.println("Cannot access field: " + fieldName);
} catch (IllegalArgumentException e) {
System.err.println("Invalid value type for field: " + fieldName);
} catch (SecurityException e) {
System.err.println("Security manager denied access to field: " + fieldName);
}
}
}
Common Mistakes to Avoid
- Not caching reflection objects: Looking up methods and fields repeatedly is expensive
- Ignoring generic type information: Use
ParameterizedType
for generic types - Forgetting about primitive vs wrapper types:
int.class != Integer.class
- Not handling inheritance properly: Use
getDeclaredMethods()
vsgetMethods()
appropriately - Memory leaks: Reflection can prevent garbage collection of classes
Advanced Reflection Techniques
Working with Generics
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
public class GenericReflection {
private List<String> stringList;
private Map<String, Integer> stringIntMap;
public static void main(String[] args) throws Exception {
Field stringListField = GenericReflection.class.getDeclaredField("stringList");
Type genericType = stringListField.getGenericType();
if (genericType instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) genericType;
Type[] actualTypes = paramType.getActualTypeArguments();
System.out.println("Raw type: " + paramType.getRawType());
System.out.println("Type arguments: " + Arrays.toString(actualTypes));
}
}
}
Dynamic Proxy Creation
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface UserService {
void createUser(String name);
String getUser(int id);
}
class UserServiceProxy implements InvocationHandler {
private final Object target;
public UserServiceProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
long startTime = System.nanoTime();
Object result = method.invoke(target, args);
long endTime = System.nanoTime();
System.out.println("Method " + method.getName() + " took " + (endTime - startTime) + " ns");
return result;
}
public static <T> T createProxy(T target, Class<T> interfaceType) {
return (T) Proxy.newProxyInstance(
interfaceType.getClassLoader(),
new Class[]{interfaceType},
new UserServiceProxy(target)
);
}
}
Reflection is extensively used in enterprise frameworks and development tools. Spring Framework uses it for dependency injection, Hibernate for ORM mapping, and JUnit for test discovery. When building applications that need to run on robust infrastructure, consider deploying on VPS or dedicated servers that can handle the additional CPU overhead that reflection introduces.
For more detailed information about Java Reflection API, check out the official Oracle documentation at https://docs.oracle.com/javase/tutorial/reflect/. The Java Platform documentation also provides comprehensive coverage of the reflection package at https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/package-summary.html.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.