How to Use Java Reflection with Methods, Constructors, and Class Types

Methods

A Method object let you get more information on the corresponding method: its type and its modifiers, the types and names of its parameters, and enables you to invoke it on a given object, passing the arguments you need. This section also covers how you can discover methods in a class.
Source: dev.java - methods

There are two ways to access the methods of a class in Java:

  1. get() method
  2. getDeclared() method

The get() To access only the public method of a class. The get() method would only return type from superclasses

  • Include all public would members of similar type from superclasses
  • public does not meant that method is accessible. we saw before that a class itself can be inaccessible

The getDeclared() methods return all members of that class only

  • includes private, protected, package access and public memvers

  • But only from the current class, not from superclasses

Example

import java.lang.reflect.Method;

class Example {
    public void publicMethod() {
        System.out.println("Public method called");
    }

    private void privateMethod() {
        System.out.println("Private method called");
    }
}

public class ReflectionDemo {
    public static void main(String[] args) {
        try {
            Class> clazz = Example.class;

            // Using getMethod() - Accesses only public methods (including inherited)
            Method publicMethod = clazz.getMethod("publicMethod");
            System.out.println("Found with getMethod: " + publicMethod.getName());

            // Using getDeclaredMethod() - Accesses any method declared in the class (including private)
            Method privateMethod = clazz.getDeclaredMethod("privateMethod");
            System.out.println("Found with getDeclaredMethod: " + privateMethod.getName());

            // To invoke private method, setAccessible(true) is needed
            privateMethod.setAccessible(true);
            privateMethod.invoke(clazz.getDeclaredConstructor().newInstance());

        } catch (NoSuchMethodException e) {
            System.out.println("Method not found: " + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Found with getMethod: publicMethod
Found with getDeclaredMethod: privateMethod
Private method called

📊 Table

Feature getXXX() getDeclaredXXX()
Access level Only public All access levels (private, etc.)
Includes inherited? ✅ Yes ❌ No
Typical use case External API access Internal inspection / deep reflection
Needs setAccessible(true)? Not usually Often yes (for private members)

Modifire & AccessFlag

The Modifier class in Java helps interpret the modifier flags (like public, private, static, etc.) of classes, fields, methods, and constructors at runtime.

Besides that, with getModifiers() you can retrieve the modifiers of a class. It returns an int representing bitwise flags, which you can decode using the java.lang.reflect.Modifier class.

import java.lang.reflect.*;

public class ModifierDemo {
    public static void main(String[] args) throws Exception {
        Class> clazz = Sample.class;

        System.out.println("Class modifiers:");
        int classModifiers = clazz.getModifiers();
        System.out.println("  Is public? " + Modifier.isPublic(classModifiers));
        System.out.println("  Is abstract? " + Modifier.isAbstract(classModifiers));

        Field field = clazz.getDeclaredField("count");
        int fieldModifiers = field.getModifiers();
        System.out.println("\nField modifiers:");
        System.out.println("  Is static? " + Modifier.isStatic(fieldModifiers));
        System.out.println("  Is final? " + Modifier.isFinal(fieldModifiers));
    }

    public static abstract class Sample {
        public static final int count = 42;
    }
}

AccessFlag is an enumeration that introduced in java 20, which are bitwise flags that represent the modifiers.
Examples of access flags in bytecode:

  • ACC_PUBLIC (0x0001)

  • ACC_PRIVATE (0x0002)

  • ACC_STATIC (0x0008)

  • ACC_FINAL (0x0010)

These are defined in the ClassFile structure of the Java Virtual Machine Specification. They’re not typically used directly in reflection, but Modifier uses these under the hood.

Constructor

Constructor is a special case of a method. we dont specify a name, but do specify the parameter types. even the exception are the same "NoSuchMethodException"

In Constructor instead of calling invoke(), we call newInstance();

public class ConstructorReflect {
    private static final List<Integer> pi = List.of(0,1,3,7,9) ;

    public static void main(String[] args) throws ReflectiveOperationException {
        createCollection(ArrayList.class.getTypeName());
    }

    public static void createCollection(String name) throws ReflectiveOperationException{

        Class extends Collection> subclass = Class.forName(name).asSubclass(Collection.class);
        System.out.println(subclass.getSimpleName());

        Constructor extends Collection> constructorNoArgs = subclass.getConstructor();
        System.out.println(constructorNoArgs.newInstance());

        Constructor extends Collection> constructor = subclass.getConstructor(Collection.class);
        System.out.println(constructor.newInstance(pi));
    }

}

🛠️ getConstructors()

Point Description
Similar to getMethod() Similar to getMethod(), but focuses on constructors.
Constructors Differentiation Constructors are differentiated only by parameter types. If the type is known, use getConstructor().
Unlike getMethods() Unlike getMethods(), does not include constructors from superclasses.
Return Value Returns all the public constructors.

Nested Class

Nested classes are classes that are declared within another class. In Java, we can discover public nested classes using the Class.getClasses() method. However, this method does not return local or anonymous classes, as they are scoped within methods and are not part of the class's members.

As mentioned previously, the getClasses() method returns only the public nested classes of a class. If we need to access private, protected, or package-private nested classes, the getDeclaredClasses() method should be used instead, as it returns all declared nested classes regardless of their access modifiers.

Sealed Classes

Sealed classes were introduced in Java 15 to provide more control over class inheritance.

  • They limit which classes can extend or implement them.

  • All permitted subclasses must be explicitly declared and must themselves be final, sealed, or non-sealed.

  • Sealed classes also support reflection, allowing us to discover their permitted subclasses at runtime.

We can use Class.getPermittedSubclasses() to retrieve the list of allowed subclasses, and Class.isSealed() to check whether a class is sealed.

public class PermittedSubClassExplorer {
    public static Set<Class>> find(Class> clazz){
        if ( !clazz.isSealed()) throw new IllegalArgumentException( clazz + " root must be sealed");

        var permittedClassSet = Arrays.stream(clazz.getPermittedSubclasses()).collect(Collectors.toSet());
        for (Class> subClass : permittedClassSet) {

            if (subClass.isSealed())  permittedClassSet.addAll(find(subClass));
        }
        return Set.copyOf(permittedClassSet);
    }
}
import com.sadeghi.reflection.exercise_1D.PermittedSubClassExplorer;
import org.junit.jupiter.api.Test;

import java.util.Set;

import static org.junit.jupiter.api.Assertions.*;

class PermittedSubClassExplorerTest {
    public sealed class Shape permits Circle, Rectangle, Square {}

    public final class Circle extends Shape {}
    public final class Rectangle extends Shape {}
    public final class Square extends Shape {}

    @Test
    void testPermittedSubClassOfShape(){

        assertAll( () -> PermittedSubClassExplorer.find(Shape.class) , ()-> Set.of(Circle.class, Rectangle.class , Square.class));
    }
}

Records

Record was added in Java 16 to provide a simplified way to define data-carrying objects.

  • They automatically generate hashCode() , equals() and toString() methods.
  • Records are immutable by default (final fields, no setters).

  • They do not follow the traditional JavaBean getter naming convention (getX()), instead, they expose component names directly as methods.

In reflection, you can use the method getRecordComponents() to retrieve the record components from a class.
There is another method to check if a class is a record:Class.isRecord()

Class> clazz = PersonRecord.class;
boolean isRecord = clazz.isRecord();
RecordComponent[] recordComponents = clazz.getRecordComponents();

JavaBean Convention

The JavaBean convention uses getters and setters like:

  • getFirstName()
  • getLastName()
  • getAge()

For boolean values, the getter typically starts with "is", e.g., isMarried().

What if we have a property called “iPhone”?
The convention would generate: getIPhone().

For records, Java moved away from the JavaBean convention, using component names directly instead of traditional getter methods.

The old, legacy approach for reading JavaBean properties is using the Introspector class.

BeanInfo info = Introspector.getBeanInfo(MyBean.class);
PropertyDescriptor[] props = info.getPropertyDescriptors();

Check out the project on GitHub

In the final part of reflection, we will explore private properties and also take a look at method invocation using invoke().