SoFunction
Updated on 2025-05-18

Share 20 tips for using reflection in Java

Java reflection is a powerful mechanism that allows programs to check and operate classes, interfaces, fields, and methods at runtime.

Despite its great flexibility, reflection is also a double-edged sword—improper use can lead to performance degradation, security vulnerabilities, and difficult-to-debug code.

This article summarizes 20 experiences about Java reflection.

Basic best practices

1. Cache reflective objects

Reflection operations to obtain objects such as Class, Method, Field, Constructor, etc. are expensive, and these objects should be cached as much as possible, especially in loops or hotspot code paths.

// Not recommended: Get method objects every time you callpublic Object invoke(Object obj, String methodName, Object... args) throws Exception {
    Method method = ().getDeclaredMethod(methodName, getParameterTypes(args));
    return (obj, args);
}

// Recommended: Use cacheprivate static final ConcurrentHashMap<Class<?>, Map<String, Method>> METHOD_CACHE = new ConcurrentHashMap<>();

public Object invoke(Object obj, String methodName, Object... args) throws Exception {
    Class<?> clazz = ();
    Map<String, Method> methods = METHOD_CACHE.computeIfAbsent(clazz, 
        k -> (())
                   .collect((Method::getName, m -> m, (m1, m2) -> m1)));
    Method method = (methodName);
    return (obj, args);
}

2. Distinguish between getMethods() and getDeclaredMethods()

  • getMethods()Return all public methods, including inherited methods
  • getDeclaredMethods()Return all methods (including private, protected, default, and public), but not inherited methods
// Get all public methods (including inherited from the parent class)Method[] publicMethods = ();

// Get all declared methods (including private methods, but not inherited methods)Method[] declaredMethods = ();

Choosing the right method can improve performance and avoid unexpected access to methods that should not be accessed.

3. Correctly handle InvocationTargetException

When using reflection to call the method, the original exception is wrapped in an InvocationTargetException, and the original exception should be extracted and processed.

try {
    (obj, args);
} catch (InvocationTargetException e) {
    // Get and handle the actual exception thrown by the target method    Throwable targetException = ();
    ("Method {} threw an exception: {}", (), ());
    throw targetException; // Or appropriately handle} catch (IllegalAccessException e) {
    // Handle access permission issues    ("Access denied to method {}: {}", (), ());
}

4. Use setAccessible(true) reasonably

usesetAccessible(true)Access checking can be bypassed to access private members, but should be used with caution.

Field privateField = ("privateField");
(true); // Allow access to private fields(instance, newValue);

In a production environment, the necessity and safety impact of such operations should be considered.

5. Use generics to enhance type safety

Generics can reduce type conversions, making reflection code safer.

// Type-unsafe reflectionObject result = (obj, args);
String strResult = (String) result; // Possible ClassCastException
// Use generics to enhance type safetypublic <T> T invokeMethod(Object obj, Method method, Object... args) throws Exception {
    @SuppressWarnings("unchecked")
    T result = (T) (obj, args);
    return result;
}

Performance optimization tips

6. Avoid reflecting hotspot paths

Avoid using reflection on performance-critical code paths. If you must use it, consider the following alternatives:

  • Use factory pattern or dependency injection
  • Pre-generated accessor code
  • Use interfaces instead of reflection
// Not recommended: Use reflection in frequently called codefor (int i = 0; i < 1000000; i++) {
    (obj, i);
}

// Recommended: Encapsulate reflection into the factory and create a caller at one timeinterface Processor {
    void process(int i);
}

Processor processor = createProcessor(obj, method);
for (int i = 0; i < 1000000; i++) {
    (i);
}

7. Consider using MethodHandle instead of reflection

MethodHandle introduced by Java 7 is usually more efficient than traditional reflection, especially when the same method is called repeatedly.

// Use MethodHandleMethodType methodType = (, );
 lookup = ();
MethodHandle handle = (, "substring", methodType);

// Call method (a hotspot path with better performance)String result = (String) ("Hello World", 6);

8. Create efficient function interface using LambdaMetafactory

For simple getter and setter methods, you can use LambdaMetafactory to create a function interface, and the performance is close to direct calls.

public static <T, R> Function<T, R> createGetter(Class<T> clazz, String propertyName) throws Exception {
    Method method = ("get" + capitalize(propertyName));
     lookup = ();
    CallSite site = (
            lookup,
            "apply",
            (),
            (, ),
            (method),
            ((), clazz));
    return (Function<T, R>) ().invokeExact();
}

// Use the generated functionFunction<Person, String> nameGetter = createGetter(, "name");
String name = (person); // Performance close to direct call()

9. Optimize reflection using third-party libraries

In some cases, consider using a library that is specifically optimized for reflection:

  • ByteBuddy
  • ReflectASM
  • CGLib
// Optimize reflection calls with ByteBuddyGetter<Person, String> nameGetter = ()
    .in()
    .getter(("name"))
    .bindTo((()));
    
String name = (person);

10. Pass large arrays or complex objects with caution

Large arrays or complex objects can cause performance problems when passing parameters or return values ​​through reflection. Consider using simpler data types or streaming.

// Not recommended: Passing large arrays through reflectionObject[] largeArray = new Object[10000];
(obj, (Object) largeArray);

// Recommended: Use smaller batches or streaming(largeArray)
      .collect((i -> i % 100))
      .forEach((batch, items) -> {
          try {
              (obj, (Object) ());
          } catch (Exception e) {
              // Handle exceptions          }
      });

Safety considerations

11. Avoid modifying final fields through reflection

While technically feasible, modifying the final field can lead to thread safety issues and unpredictable behavior.

// Dangerous operation: Modify final fieldField field = ("CONSTANT");
(true);
Field modifiersField = ("modifiers");
(true);
(field, () & ~);
(null, newValue); // May cause serious problems

This operation becomes more difficult after Java 9 and may theoretically cause the JVM optimization assumption to fail.

12. Access control check

In frameworks or APIs, appropriate access control checks should be implemented to prevent malicious use of reflections.

public void invokeMethod(Object target, String methodName, Object... args) {
    // Check whether the caller has permission to perform this operation    SecurityManager sm = ();
    if (sm != null) {
        (new ReflectPermission("suppressAccessChecks"));
    }
    
    // Check whether the target method is in the whitelist of allowed calls    if (!ALLOWED_METHODS.contains(methodName)) {
        throw new SecurityException("Method not in allowed list: " + methodName);
    }
    
    // Perform reflection operation    // ...
}

13. Pay attention to the differences in JDK versions

Different JDK versions have different restrictions on reflection, and module systems after Java 9 have added more restrictions on reflection access.

// Java 9+ access classes that are not exported modulestry {
    // Try to use reflection access    Class<?> clazz = ("");
    // This throws an exception unless the JVM is started with the --add-opens parameter} catch (Exception e) {
    // Handle access restriction exceptions}

In Java 11+, it is recommended to explicitly specify modules that need to be opened in the startup parameters:

--add-opens /=ALL-UNNAMED

14. Handle the security issues of dynamic proxy

When using reflection and dynamic proxying, make sure the proxy class is not abused.

// Secure proxy creationInvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) (
        (),
        new Class<?>[] {  },
        (proxy, method, args) -> {
            // Check whether this method is allowed to be called            if (() == ) {
                return (handler, args); // Allow Object methods            }
            
            if (!ALLOWED_METHODS.contains(())) {
                throw new SecurityException("Method not allowed: " + ());
            }
            
            return (target, args);
        });

15. Avoid reflective calls to serialize/deserialize methods

Do not use reflection callsreadObjectwriteObjectequal serialization method, which can lead to serious security vulnerabilities.

// Dangerous operationMethod readObject = ("readObject", );
(true);
(instance, inputStream); // May cause deserialization vulnerabilities

Java standard serialization mechanism or a secure serialization library should be used.

Advanced skills and practices

16. Implement declarative programming in combination with annotations

Combining reflection and annotation can implement a powerful declarative programming model, similar to Spring and JPA implementations.

// Custom annotations@Retention()
@Target()
public @interface Transactional {
    boolean readOnly() default false;
}

// Use reflection processing annotationpublic void processClass(Class<?> clazz) {
    for (Method method : ()) {
        Transactional annotation = ();
        if (annotation != null) {
            boolean readOnly = ();
            // Create a transaction agent...        }
    }
}

17. Get generic information

Although Java has type erasure, generic information can be obtained through the reflection API.

public class GenericExample<T> {
    private List<String> stringList;
    private Map<Integer, T> genericMap;
    
    // Get field generic type    public static void main(String[] args) throws Exception {
        Field stringListField = ("stringList");
        ParameterizedType stringListType = (ParameterizedType) ();
        Class<?> stringListClass = (Class<?>) ()[0];
        (stringListClass); // Output: class        
        Field genericMapField = ("genericMap");
        ParameterizedType genericMapType = (ParameterizedType) ();
        Type keyType = ()[0]; // Integer
        Type valueType = ()[1]; // T (TypeVariable)
        (keyType + ", " + valueType);
    }
}

18. Implement plug-in system and SPI mechanism

Reflection is a key technology for implementing plug-in systems and service provider interfaces (SPIs).

public class PluginLoader {
    public static List<Plugin> loadPlugins(String packageName) {
        List<Plugin> plugins = new ArrayList<>();
        
        // Scan the class in the package        Reflections reflections = new Reflections(packageName);
        Set<Class<? extends Plugin>> pluginClasses = 
            ();
            
        // Instantiate the plugin        for (Class<? extends Plugin> pluginClass : pluginClasses) {
            try {
                // Find @PluginInfo annotation                PluginInfo info = ();
                if (info != null && ()) {
                    // Create plugin instance using reflection                    Plugin plugin = ().newInstance();
                    (plugin);
                }
            } catch (Exception e) {
                // Handle exceptions            }
        }
        
        return plugins;
    }
}

19. Avoid reflection to create native arrays of types

Special attention is required to create native arrays of types, and you cannot use ().

// Error: Try to create int[] through reflectionObject array = (, 10); // correctObject array = (, 10); // Also correct
// Error: Try to set the value by reflection(array, 0, 42); // Throw IllegalArgumentException// The correct way(array, 0, 42);

20. Simulate dependency injection using reflection

A simple dependency injection framework can be implemented using reflection, similar to the core functionality of Spring.

public class SimpleDI {
    private Map<Class<?>, Object> container = new HashMap<>();
    
    public void register(Class<?> type, Object instance) {
        (type, instance);
    }
    
    public <T> T getInstance(Class<T> type) throws Exception {
        // Check if there is an instance in the container        if ((type)) {
            return ((type));
        }
        
        // Create a new instance        Constructor<T> constructor = ();
        T instance = ();
        
        // Inject field        for (Field field : ()) {
            if (()) {
                (true);
                Class<?> fieldType = ();
                Object dependency = getInstance(fieldType); // Recursively parse dependencies                (instance, dependency);
            }
        }
        
        (type, instance);
        return instance;
    }
}

Summarize

In practical applications, the flexibility brought by reflection should be weighed against the potential performance, safety and maintainability issues.

In most cases, if there is an alternative to not using reflection, it should be given priority.

Finally, excellent framework designs should try to encapsulate reflection details, providing end users with a clear, type-safe API, using reflection only in necessary internal implementations.

The above is the detailed content shared by 20 usage techniques for reflection in Java. For more information about Java reflection techniques, please follow my other related articles!