java getDeclaredMethods: How to Use Reflection
Learn how to use Java reflection's getDeclaredMethods() to inspect class methods, access private methods, and avoid common pitfalls.
Java's getDeclaredMethods() reflection API lets you inspect the methods declared directly by a class at runtime. It returns a Method[] containing all methods declared in the class—regardless of access modifier—but excludes inherited methods. This makes it the primary tool for discovering private, protected, package-private, and public methods that are defined in the class itself, rather than methods inherited from a superclass or interface.
What getDeclaredMethods() Returns
The Class.getDeclaredMethods() method returns an array of Method objects representing all methods declared by the class or interface. This includes static, instance, abstract, final, and native methods. It does not include methods inherited from a superclass, nor default methods from interfaces unless the class itself overrides or re-declares them. Calling it directly on an interface includes the default methods declared by that interface.
For example, given the following class:
public class UserService { public void createUser() { } private void validateEmail() { } protected void logAccess() { } void sendNotification() { } }
Calling UserService.class.getDeclaredMethods() returns an array containing all four methods, even though validateEmail is private and sendNotification is package-private. The order of the array is not specified by the Java Language Specification (JLS); it depends on the JVM implementation and can vary between runs.
Accessing Private and Non-Public Methods
Because getDeclaredMethods() includes non-public methods, you can invoke them using reflection, but you must first call setAccessible(true) on the Method object. This suppresses Java language access checks for that specific method. Without it, invoking a private method throws an IllegalAccessException.
Method validateEmail = UserService.class.getDeclaredMethod("validateEmail"); validateEmail.setAccessible(true); validateEmail.invoke(new UserService());
This works in a plain Java application, but the module system introduced in Java 9 adds restrictions. If the target class is in a named module, the package must be open to the caller's module using --add-opens or an opens directive in the module descriptor. Otherwise, setAccessible(true) may throw an InaccessibleObjectException.
Method Order and Determinism
The JVM does not guarantee any specific order for the array returned by getDeclaredMethods(). The order can differ across JVM versions, or even between runs of the same program. Therefore, you should never rely on array indices to identify a specific method. Instead, filter by method name, parameter types, or annotations.
If you need a stable ordering, sort the array manually. For example, you can sort by method name and then by parameter count:
Method[] methods = clazz.getDeclaredMethods(); Arrays.sort(methods, Comparator.comparing(Method::getName) .thenComparingInt(Method::getParameterCount));
Sorting is particularly important when you use reflection to generate documentation, build serialization logic, or implement dependency injection that must behave consistently across environments.
Handling Synthetic and Bridge Methods
getDeclaredMethods() includes synthetic methods generated by the compiler for purposes like nested class access or enum switch statements. It also includes bridge methods, which are synthetic methods that preserve polymorphism when generic types are erased. These methods are marked with the synthetic or bridge modifiers, which you can check using Method.isSynthetic() or Method.isBridge().
For most reflection-based tools, you'll want to filter these out to avoid exposing implementation details. Here's a common filtering pattern:
List<Method> userMethods = Arrays.stream(clazz.getDeclaredMethods()) .filter(m -> !m.isSynthetic() && !m.isBridge()) .collect(Collectors.toList());
This keeps only the methods that the developer actually wrote. Failing to filter can lead to confusing results, such as seeing extra access$000 methods or generic bridge methods that duplicate your source-level methods.
Performance and Caching Considerations
Reflection can be significantly slower than direct method calls because the JVM must perform dynamic lookup and access checks. Calling getDeclaredMethods() itself is relatively cheap, but invoking the returned Method objects repeatedly can become a bottleneck. If your code inspects the same class multiple times, cache the Method[] array or the individual Method objects rather than re-querying the class each time.
private static final Method[] USER_METHODS = UserService.class.getDeclaredMethods();
Be aware that caching Method objects does not make invocation faster; it only avoids the repeated class metadata lookup. For performance-critical paths, consider using java.lang.invoke.MethodHandle or a code-generation library like Byte Buddy, which can offer better runtime performance than classic reflection.
Common Pitfalls with getDeclaredMethods()
One frequent mistake is assuming that getDeclaredMethods() returns methods from superclasses. It does not. If you need the full set of accessible methods, including inherited public methods, use getMethods() instead. However, getMethods() only returns public methods, so it cannot return non-public inherited methods.
Another pitfall is ignoring exceptions. getDeclaredMethods() throws SecurityException if a security manager is present and denies access. In modern Java, security managers are deprecated, but they can still appear in legacy environments. Also, when dealing with array classes or primitive types, getDeclaredMethods() returns an empty array because those types do not declare methods.
Finally, be careful when calling setAccessible(true) on methods from classes in named modules. It works for application classes in the unnamed module, but for classes in Java platform modules you must first open the relevant package. This is why you cannot easily reflectively invoke private methods in java.lang classes without JVM flags.
Practical Example: Inspecting a Class's Methods
Here's a complete example that lists all non-synthetic, non-bridge methods declared by a class, showing their modifiers and parameter types:
import java.lang.reflect.*; import java.util.*; import java.util.stream.*; public class MethodInspector { public static void main(String[] args) { Class<?> clazz = UserService.class; List<Method> methods = Arrays.stream(clazz.getDeclaredMethods()) .filter(m -> !m.isSynthetic() && !m.isBridge()) .collect(Collectors.toList()); for (Method method : methods) { System.out.println(Modifier.toString(method.getModifiers()) + " " + method.getReturnType().getSimpleName() + " " + method.getName() + "(" + Arrays.stream(method.getParameterTypes()) .map(Class::getSimpleName) .collect(Collectors.joining(", ")) + ")"); } } }
This code filters out compiler-generated methods and prints a readable signature for each declared method. It works for any class assigned to clazz, making it a useful utility for debugging or building framework-like features.
When to Use getDeclaredMethods() Instead of getMethods()
The choice between getDeclaredMethods() and getMethods() depends on what you need. Use getDeclaredMethods() when you must access non-public methods, or when you need to distinguish between methods declared in the class and those inherited. This is common in frameworks that inspect annotations on non-public methods, or that need to know which methods are declared directly by a class rather than inherited.
Use getMethods() when you only need public methods, including inherited ones, and you don't care about the declaring class. It is simpler than walking the class hierarchy, but it can still include synthetic bridge methods, so filter them if they matter.
The table below summarizes the key differences:
| Criterion | getDeclaredMethods() | getMethods() |
|---|---|---|
| Includes private methods | Yes | No |
| Includes inherited methods | No | Yes (public only) |
| Includes synthetic methods | Yes, unless filtered | Yes, unless filtered |
| Typical use case | Full class introspection | Public API discovery |
In practice, most reflection-heavy libraries use getDeclaredMethods() because they need access to non-public methods and can filter inherited methods manually when required. Understanding the exact behavior of this method helps you avoid subtle bugs when building serializers, ORMs, or test runners that rely on runtime method discovery.