PYnative

Python Programming

  • Learn Python ▼
    • Python Tutorials
    • Python Basics
    • Python Interview Q&As
  • Exercises ▼
    • Python Exercises
    • C++ Exercises
    • C Programming Exercises
    • Java Exercises
    • C# Exercises
  • Quizzes
  • Online Compilers▼
    • Online Python Code Editor
    • Online C Compiler
    • Online C++ Compiler
    • Online Java Compiler
    • Online C# Compiler
Home » Java Exercises » Java Reflection Exercises: 20 Coding Problems with Solutions

Java Reflection Exercises: 20 Coding Problems with Solutions

Updated on: July 6, 2026 | Leave a Comment

This collection of 20 Java reflection exercises starts with basic class inspection and builds up to the same techniques real frameworks use for testing, dependency injection, and serialization.

  • Early exercises cover loading classes dynamically with Class.forName(), reading modifiers and interfaces, and bypassing access control to read and write private fields and constructors.
  • The middle set covers method inspection and dynamic invocation, working with generic type information, and using java.lang.reflect.Array for runtime array creation.
  • The final exercises combine reflection with annotations to build a mini JUnit-style test runner, a small dependency injection container, a recursive toString() generator, and a command-line dispatcher.

Each exercise includes a Problem Statement, Purpose, Hint, and a Solution with Explanation that clarifies exactly what each reflective call is doing under the hood.

  • Also, See: Java Exercises with over 20+ topic-wise sets and 575+ coding questions to practice.
  • Practice questions using our Online Java Compiler
+ Table of Contents (20 Exercises)

Table of contents

  • Exercise 1: The Class Locator
  • Exercise 2: Access Modifier Auditor
  • Exercise 3: Interface Tracker
  • Exercise 4: Field Blueprint
  • Exercise 5: Breaking Encapsulation
  • Exercise 6: Dynamic Config Injector
  • Exercise 7: Clearing Static States
  • Exercise 8: The No-Arg Instantiator
  • Exercise 9: Private Constructor Breach
  • Exercise 10: Multi-Arg Constructor Selection
  • Exercise 11: Method Inventory
  • Exercise 12: Dynamic Invocation
  • Exercise 13: Private Executioner
  • Exercise 14: Dynamic Array Expansion
  • Exercise 15: Generic Type Erasure Inspector
  • Exercise 16: Custom JSON Serializer
  • Exercise 17: Build a Mini-JUnit Framework
  • Exercise 18: Dependency Injection (DI) Container
  • Exercise 19: Deep toString() Generator
  • Exercise 20: Command-Line Route Dispatcher

Exercise 1: The Class Locator

Problem Statement: Write a program that takes a fully qualified class name as a string (e.g., "java.util.ArrayList"), loads it dynamically using Class.forName(), and prints its package name, simple name, and superclass name.

Purpose: This exercise helps you practice loading a class at runtime purely from its name, a foundational reflection technique used by frameworks that need to work with classes they never see at compile time.

Given Input: "java.util.ArrayList"

Expected Output:

Package: java.util
Simple name: ArrayList
Superclass: java.util.AbstractList
▼ Hint
  • Class.forName(className) loads and initializes a class dynamically at runtime, given its fully qualified name as a string.
  • getPackageName() and getSimpleName() read the loaded class’s metadata without needing a compile-time reference to the class.
  • getSuperclass() returns a Class<?> object for the immediate parent class, so calling getName() on that gives its fully qualified name.
▼ Solution & Explanation
public class Main {

    public static void inspectClass(String className) throws ClassNotFoundException {
        Class<?> clazz = Class.forName(className);
        System.out.println("Package: " + clazz.getPackageName());
        System.out.println("Simple name: " + clazz.getSimpleName());
        System.out.println("Superclass: " + clazz.getSuperclass().getName());
    }

    public static void main(String[] args) throws ClassNotFoundException {
        // Usage:
        inspectClass("java.util.ArrayList");
    }
}Code language: Java (java)

Explanation:

  • Class.forName(className): Looks up and loads the class matching the given fully qualified name, throwing ClassNotFoundException if no such class exists on the classpath.
  • clazz.getPackageName(): Reads the package portion of the class’s fully qualified name.
  • clazz.getSuperclass().getName(): Retrieves the direct parent class as a Class<?> object, then reads its fully qualified name.
  • Alternative: You could catch ClassNotFoundException locally inside inspectClass() and print an error message instead of letting it propagate, if the calling code should not have to handle the exception itself.

Exercise 2: Access Modifier Auditor

Problem Statement: Given any Class<?> object, extract and decode its modifiers. Print whether the class is public, abstract, final, or an interface using the Modifier utility class.

Purpose: This exercise helps you practice decoding a class’s modifier bitmask with the Modifier utility class, the same mechanism reflection uses internally to answer access and structural questions about any class.

Given Input: List.class

Expected Output:

Public: true
Abstract: true
Final: false
Interface: true
▼ Hint
  • getModifiers() returns an int where each bit represents a different modifier, such as public, abstract, or final.
  • The Modifier utility class provides static methods like isPublic(), isAbstract(), and isFinal() that decode individual bits from that int.
  • Use isInterface() directly on the Class<?> object to check for the interface modifier, since Modifier itself does not expose a dedicated method for it.
▼ Solution & Explanation
import java.lang.reflect.Modifier;

public class Main {

    public static void auditModifiers(Class<?> clazz) {
        int modifiers = clazz.getModifiers();
        System.out.println("Public: " + Modifier.isPublic(modifiers));
        System.out.println("Abstract: " + Modifier.isAbstract(modifiers));
        System.out.println("Final: " + Modifier.isFinal(modifiers));
        System.out.println("Interface: " + clazz.isInterface());
    }

    public static void main(String[] args) {
        // Usage:
        auditModifiers(java.util.List.class);
    }
}Code language: Java (java)

Explanation:

  • clazz.getModifiers(): Returns a bitmask int encoding every modifier that applies to the class.
  • Modifier.isPublic(modifiers) / isAbstract(modifiers) / isFinal(modifiers): Each checks one specific bit within that bitmask and returns a boolean.
  • clazz.isInterface(): A separate convenience method on Class<?> itself, since checking for the interface modifier through Modifier would require comparing against Modifier.INTERFACE directly.
  • Alternative: You could call Modifier.toString(modifiers) to get a single human-readable string listing every modifier at once, instead of checking each one individually.

Exercise 3: Interface Tracker

Problem Statement: Write a utility method that accepts an object and recursively lists all the interfaces implemented by its class and its parent superclasses up the hierarchy.

Purpose: This exercise helps you practice walking a class hierarchy with getSuperclass(), since getInterfaces() alone only reports the interfaces declared directly on one class, not inherited ones.

Expected Output: [java.util.List, java.util.RandomAccess, java.lang.Cloneable, java.io.Serializable]

▼ Hint
  • Start with obj.getClass() to get the runtime class of the given object, not just its declared type.
  • Loop while the current class reference is not null, calling getInterfaces() on it each time to collect the interfaces declared directly on that class.
  • Move up the hierarchy with getSuperclass() after each iteration, since a class only reports the interfaces it declares itself, not those declared by its ancestors.
▼ Solution & Explanation
import java.util.ArrayList;
import java.util.List;

public class Main {

    public static List<String> listAllInterfaces(Object obj) {
        List<String> interfaceNames = new ArrayList<>();
        Class<?> current = obj.getClass();

        while (current != null) {
            for (Class<?> iface : current.getInterfaces()) {
                interfaceNames.add(iface.getName());
            }
            current = current.getSuperclass();
        }

        return interfaceNames;
    }

    public static void main(String[] args) {
        // Usage:
        List<String> interfaces = listAllInterfaces(new ArrayList<String>());
        System.out.println(interfaces);
    }
}Code language: Java (java)

Explanation:

  • obj.getClass(): Returns the exact runtime class of the object, which matters if obj was declared using a more general reference type.
  • current.getInterfaces(): Returns only the interfaces implemented directly by that specific class, not by its superclasses.
  • current = current.getSuperclass(): Walks one level up the class hierarchy, so the loop eventually visits every ancestor class up to Object.
  • Alternative: You could use a Set<String> instead of a List<String> to automatically avoid duplicate interface names when the same interface is implemented at multiple levels of the hierarchy.

Exercise 4: Field Blueprint

Problem Statement: Inspect a target class and print out all declared fields along with their data types and access modifiers, regardless of whether they are public, protected, or private.

Purpose: This exercise helps you practice using getDeclaredFields() to see every field on a class, unlike getFields(), which only exposes public members.

Expected Output:

public String publicField
protected int protectedField
private boolean privateField
▼ Hint
  • getDeclaredFields() returns every field declared directly on the class, including private ones, unlike getFields(), which only returns public members.
  • field.getType().getSimpleName() reads the field’s declared data type without needing the fully qualified name.
  • Modifier.toString(field.getModifiers()) converts the field’s modifier bitmask into a readable string like “private” or “protected”.
▼ Solution & Explanation
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class Main {

    static class SampleClass {
        public String publicField;
        protected int protectedField;
        private boolean privateField;
    }

    public static void printFieldBlueprint(Class<?> clazz) {
        for (Field field : clazz.getDeclaredFields()) {
            String modifiers = Modifier.toString(field.getModifiers());
            System.out.println(modifiers + " " + field.getType().getSimpleName() + " " + field.getName());
        }
    }

    public static void main(String[] args) {
        // Usage:
        printFieldBlueprint(SampleClass.class);
    }
}Code language: Java (java)

Explanation:

  • clazz.getDeclaredFields(): Returns an array of Field objects for every field declared in the class itself, regardless of access level.
  • field.getType(): Returns a Class<?> representing the field’s declared data type.
  • Modifier.toString(field.getModifiers()): Formats the field’s modifier bitmask as a readable string, such as “private” or “public”.
  • Alternative: getDeclaredFields() already excludes inherited fields by definition, so if you specifically wanted to include fields from superclasses too, you would need to walk the hierarchy manually the same way as the InterfaceTracker exercise.

Exercise 5: Breaking Encapsulation

Problem Statement: Create a class with a private String secretToken = "Secure123"; field and no getter. Write a reflection routine to bypass the access control check, retrieve the value, and print it.

Purpose: This exercise helps you practice using setAccessible(true) to read a private field’s value directly, and understand why relying on encapsulation alone is not a security guarantee against reflection.

Expected Output: Secret token: Secure123

▼ Hint
  • getDeclaredField(fieldName) locates a field by name regardless of its access level, including private fields.
  • field.setAccessible(true) suppresses Java’s normal access control check for that specific Field object, letting reflection bypass the private modifier.
  • field.get(target) reads the value of the field on the given instance, and needs to be cast back to the expected type since Field.get() returns Object.
▼ Solution & Explanation
import java.lang.reflect.Field;

class SecureConfig {
    private String secretToken = "Secure123";
}

public class Main {

    public static String readPrivateField(Object target, String fieldName) throws NoSuchFieldException, IllegalAccessException {
        Field field = target.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        return (String) field.get(target);
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        SecureConfig config = new SecureConfig();

        // Usage:
        String token = readPrivateField(config, "secretToken");
        System.out.println("Secret token: " + token);
    }
}Code language: Java (java)

Explanation:

  • target.getClass().getDeclaredField(fieldName): Finds the field by its name on the object’s runtime class, private or not.
  • field.setAccessible(true): Disables the usual access checks for this Field object, allowing get() to succeed despite the field being private.
  • (String) field.get(target): Retrieves the field’s current value from the specific target instance, cast from Object back to String.
  • Alternative: You could catch the checked exceptions inside readPrivateField() and rethrow an unchecked RuntimeException instead, which simplifies the method signature for callers who do not want to handle checked exceptions themselves.

Exercise 6: Dynamic Config Injector

Problem Statement: Write a method public static void injectField(Object target, String fieldName, Object value). Use reflection to find the specified field on the target object and change its value, bypassing private restrictions if necessary.

Purpose: This exercise helps you practice writing to a private field with reflection, building directly on the read-only technique from the previous exercise.

Given Input: field "environment", new value "production"

Expected Output: AppConfig{environment='production'}

▼ Hint
  • Locate the field with getDeclaredField(fieldName) so private fields can be found by name.
  • Call setAccessible(true) before writing to the field, the same bypass used to read a private field.
  • Use field.set(target, value) to overwrite the field’s current value on the given instance.
▼ Solution & Explanation
import java.lang.reflect.Field;

class AppConfig {
    private String environment = "development";

    @Override
    public String toString() {
        return "AppConfig{environment='" + environment + "'}";
    }
}

public class Main {

    public static void injectField(Object target, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field = target.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(target, value);
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        AppConfig config = new AppConfig();

        // Usage:
        injectField(config, "environment", "production");
        System.out.println(config);
    }
}Code language: Java (java)

Explanation:

  • target.getClass().getDeclaredField(fieldName): Finds the field on the target object’s class by name, regardless of its access modifier.
  • field.setAccessible(true): Suppresses the access control check for this Field object, so the following set() call is allowed to succeed on a private field.
  • field.set(target, value): Writes the new value into the field on the specific target instance.
  • Alternative: You could add a check that verifies value is assignment-compatible with field.getType() before calling set(), so a mismatched type throws a clearer custom error instead of an IllegalArgumentException from the reflection API.

Exercise 7: Clearing Static States

Problem Statement: Write a clean-up method that takes a class object, searches for all static fields of type Map or List, and invokes their .clear() methods to reset memory states between unit tests.

Purpose: This exercise helps you practice filtering fields by both their modifiers and their declared type, then reading a static field’s value without needing any instance of the class.

Expected Output:

Cache size: 0
History size: 0
▼ Hint
  • Loop through getDeclaredFields() and check Modifier.isStatic(field.getModifiers()) to skip instance fields entirely.
  • Use Map.class.isAssignableFrom(field.getType()) and List.class.isAssignableFrom(field.getType()) to detect fields whose declared type is a Map or a List implementation.
  • Pass null to field.get(null) when reading a static field’s value, since static fields belong to the class itself rather than to any particular instance.
▼ Solution & Explanation
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class TestCache {
    static Map<String, String> cache = new HashMap<>();
    static List<String> history = new ArrayList<>();
}

public class Main {

    public static void clearStaticCollections(Class<?> clazz) throws IllegalAccessException {
        for (Field field : clazz.getDeclaredFields()) {
            if (Modifier.isStatic(field.getModifiers())
                    && (Map.class.isAssignableFrom(field.getType()) || List.class.isAssignableFrom(field.getType()))) {
                field.setAccessible(true);
                Object value = field.get(null);
                if (value instanceof Map) {
                    ((Map<?, ?>) value).clear();
                } else if (value instanceof List) {
                    ((List<?>) value).clear();
                }
            }
        }
    }

    public static void main(String[] args) throws IllegalAccessException {
        TestCache.cache.put("key1", "value1");
        TestCache.history.add("entry1");

        // Usage:
        clearStaticCollections(TestCache.class);
        System.out.println("Cache size: " + TestCache.cache.size());
        System.out.println("History size: " + TestCache.history.size());
    }
}Code language: Java (java)

Explanation:

  • Modifier.isStatic(field.getModifiers()): Filters the loop down to only static fields, since instance fields would need an actual object instance to read from.
  • Map.class.isAssignableFrom(field.getType()): Checks whether the field’s declared type is Map or one of its subtypes, correctly matching implementations like HashMap too.
  • field.get(null): Reads a static field’s current value, passing null since static fields are not tied to any specific instance.
  • Alternative: You could restrict the type check to exact matches with field.getType() == Map.class, but isAssignableFrom() is more useful here since it also matches concrete implementations like HashMap or ArrayList.

Exercise 8: The No-Arg Instantiator

Problem Statement: Dynamically instantiate an object of a class given only its String name using getDeclaredConstructor().newInstance(), and handle all possible reflection exceptions cleanly.

Purpose: This exercise helps you practice combining dynamic class loading with dynamic instantiation, and handling the several checked exceptions reflection based object creation can throw.

Given Input: "java.util.ArrayList"

Expected Output: Created: []

▼ Hint
  • Class.forName(className) loads the class first, before any instance can be created.
  • getDeclaredConstructor().newInstance() looks up the no-argument constructor and invokes it to build a new instance.
  • Reflection based instantiation can throw several checked exceptions at once, so catch ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, and InvocationTargetException together with a multi-catch block.
▼ Solution & Explanation
import java.lang.reflect.InvocationTargetException;

public class Main {

    public static Object createInstance(String className) {
        try {
            Class<?> clazz = Class.forName(className);
            return clazz.getDeclaredConstructor().newInstance();
        } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException
                 | IllegalAccessException | InvocationTargetException e) {
            System.out.println("Failed to instantiate " + className + ": " + e.getClass().getSimpleName());
            return null;
        }
    }

    public static void main(String[] args) {
        // Usage:
        Object instance = createInstance("java.util.ArrayList");
        System.out.println("Created: " + instance);
    }
}Code language: Java (java)

Explanation:

  • Class.forName(className): Dynamically loads the class matching the given name.
  • clazz.getDeclaredConstructor().newInstance(): Finds the no-argument constructor on that class and calls it, producing a new object.
  • multi-catch block: Handles every reflection related checked exception in one place, since any of them can occur depending on what goes wrong: a missing class, a missing constructor, an abstract class, an inaccessible constructor, or an exception thrown by the constructor itself.
  • Alternative: You could split the multi-catch into separate catch blocks with more specific error messages for each failure type, which is more verbose but gives callers a clearer reason when something goes wrong.

Exercise 9: Private Constructor Breach

Problem Statement: Create a classic Singleton class with a private constructor. Write an exercise that uses reflection to force-instantiate a second instance of this Singleton, demonstrating why reflection can break the pattern.

Purpose: This exercise helps you practice locating and bypassing a private constructor with reflection, and understand a real limitation of the classic Singleton pattern that the enum Singleton from the Java Enums series does not share.

Expected Output: Same instance: false

▼ Hint
  • getDeclaredConstructor() locates the private no-argument constructor directly, since getConstructor() only finds public constructors.
  • Call setAccessible(true) on the Constructor object to bypass the access check that would normally prevent calling a private constructor from outside the class.
  • Comparing the two references with == shows they are different objects, proving reflection broke the Singleton guarantee.
▼ Solution & Explanation
import java.lang.reflect.Constructor;

class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        Singleton first = Singleton.getInstance();

        // Usage:
        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton second = constructor.newInstance();

        System.out.println("Same instance: " + (first == second));
    }
}Code language: Java (java)

Explanation:

  • Singleton.class.getDeclaredConstructor(): Finds the private no-argument constructor, which getConstructor() alone cannot see since that method only returns public constructors.
  • constructor.setAccessible(true): Suppresses the access control check, allowing newInstance() to call the private constructor from outside the class.
  • first == second: Compares object references rather than field values, revealing that a second, distinct Singleton instance was created.
  • Alternative: You could defend against this specific attack by throwing an exception inside the constructor if INSTANCE is already set, which detects the reflective bypass attempt and refuses to create a second object.

Exercise 10: Multi-Arg Constructor Selection

Problem Statement: Create a class with multiple constructors (e.g., User(String name), User(String name, int age)). Write a routine that looks up the two-argument constructor explicitly by passing the correct Class<?> array types, and instantiates the object.

Purpose: This exercise helps you practice selecting one specific overloaded constructor by its exact parameter types, rather than relying on the no-argument constructor lookup used in earlier exercises.

Given Input: "Alice", 30

Expected Output: User{name='Alice', age=30}

▼ Hint
  • getDeclaredConstructor() accepts a varargs list of Class<?> objects describing the exact parameter types of the constructor you want to find.
  • Pass String.class and int.class, in that order, to match the User(String name, int age) constructor specifically.
  • newInstance() then takes the actual argument values in the same order as the parameter types used to look up the constructor.
▼ Solution & Explanation
import java.lang.reflect.Constructor;

class User {
    private final String name;
    private final int age;

    public User(String name) {
        this(name, 0);
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + "}";
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        // Usage:
        Constructor<User> twoArgConstructor = User.class.getDeclaredConstructor(String.class, int.class);
        User user = twoArgConstructor.newInstance("Alice", 30);
        System.out.println(user);
    }
}Code language: Java (java)

Explanation:

  • User.class.getDeclaredConstructor(String.class, int.class): Searches for a constructor whose parameter types exactly match String and int, in that order, distinguishing it from the single-argument User(String name) constructor.
  • int.class: Refers to the primitive type token, which must be used here rather than Integer.class, since the constructor parameter itself is declared as a primitive int.
  • twoArgConstructor.newInstance("Alice", 30): Invokes the located constructor with the given arguments, autoboxing 30 into the primitive int parameter as needed.
  • Alternative: You could use getDeclaredConstructors() to list every constructor on the class and manually inspect each one’s getParameterTypes(), though getDeclaredConstructor() with an explicit parameter list is far more direct when you already know which signature you need.

Exercise 11: Method Inventory

Problem Statement: Print all methods of a class, including their return types, names, and a comma-separated list of their parameter types.

Purpose: This exercise helps you practice inspecting a class’s methods with reflection, the method-level counterpart to the field inspection from the Field Blueprint exercise.

Expected Output:

void start()
int calculate(int, int)
String describe(String, boolean)

(the printed order is not guaranteed by the JVM and may vary between runs)

▼ Hint
  • getDeclaredMethods() returns every method declared directly on the class, including private ones, similar to getDeclaredFields() for fields.
  • method.getReturnType().getSimpleName() reads the method’s return type, and method.getParameterTypes() returns an array of the parameter types in declaration order.
  • Join the parameter type names with Collectors.joining(", ") to build a comma-separated list for display.
▼ Solution & Explanation
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.stream.Collectors;

public class Main {

    static class SampleService {
        public void start() {
        }

        public int calculate(int a, int b) {
            return a + b;
        }

        private String describe(String prefix, boolean verbose) {
            return prefix;
        }
    }

    public static void printMethodInventory(Class<?> clazz) {
        for (Method method : clazz.getDeclaredMethods()) {
            String paramTypes = Arrays.stream(method.getParameterTypes())
                    .map(Class::getSimpleName)
                    .collect(Collectors.joining(", "));
            System.out.println(method.getReturnType().getSimpleName() + " " + method.getName() + "(" + paramTypes + ")");
        }
    }

    public static void main(String[] args) {
        // Usage:
        printMethodInventory(SampleService.class);
    }
}Code language: Java (java)

Explanation:

  • clazz.getDeclaredMethods(): Returns an array of Method objects for every method declared in the class itself, regardless of access level.
  • method.getParameterTypes(): Returns a Class<?>[] describing each parameter’s type, in the order the parameters were declared.
  • Arrays.stream(...).map(Class::getSimpleName).collect(Collectors.joining(", ")): Converts that array of types into a single readable, comma-separated string.
  • Alternative: You could use method.getGenericParameterTypes() instead of getParameterTypes() if you also needed to preserve generic type information like List<String> rather than just List.

Exercise 12: Dynamic Invocation

Problem Statement: Create a Calculator class with an add(int a, int b) method. Use reflection to look up this method by its name and parameter signature, then dynamically invoke() it on a Calculator instance.

Purpose: This exercise helps you practice looking up a specific method by its exact signature and calling it dynamically, the method equivalent of the constructor lookup from the Multi-Arg Constructor Selection exercise.

Given Input: 10, 5

Expected Output: Result: 15

▼ Hint
  • getMethod(name, parameterTypes...) looks up a public method by its exact name and parameter type signature.
  • Pass int.class twice, matching the two int parameters declared on add(int a, int b).
  • method.invoke(instance, args...) calls the method on the given object, passing the arguments in the same order as the parameter types used to look it up.
▼ Solution & Explanation
import java.lang.reflect.Method;

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        Calculator calculator = new Calculator();

        // Usage:
        Method addMethod = Calculator.class.getMethod("add", int.class, int.class);
        Object result = addMethod.invoke(calculator, 10, 5);
        System.out.println("Result: " + result);
    }
}Code language: Java (java)

Explanation:

  • Calculator.class.getMethod("add", int.class, int.class): Finds the public add method whose parameters are exactly two ints.
  • addMethod.invoke(calculator, 10, 5): Calls that method on the calculator instance, passing 10 and 5 as arguments, and autoboxing the returned int into an Integer.
  • Object result: invoke() always returns Object, since reflection has no compile-time knowledge of the method’s actual return type.
  • Alternative: You could use getDeclaredMethod() instead of getMethod() if add() were private or package-private, since getMethod() only finds public methods, including inherited ones.

Exercise 13: Private Executioner

Problem Statement: Write a generic method public static Object runPrivateMethod(Object obj, String methodName, Object... args). This should find and run any hidden private method on the object and return the result.

Purpose: This exercise helps you practice locating and invoking a private method by scanning getDeclaredMethods(), the method equivalent of the private field access from the Breaking Encapsulation exercise.

Given Input: method name "square", argument 7

Expected Output: Result: 49

▼ Hint
  • Loop through getDeclaredMethods() instead of calling getMethod() directly, since private methods are invisible to getMethod() but visible to getDeclaredMethods().
  • Match candidates by both name and parameter count, since a class could have several overloaded methods sharing the same name.
  • Call setAccessible(true) on the matching Method before invoking it, the same bypass used earlier for private fields and constructors.
▼ Solution & Explanation
import java.lang.reflect.Method;

public class Main {

    static class SecretOperations {
        private int square(int n) {
            return n * n;
        }
    }

    public static Object runPrivateMethod(Object obj, String methodName, Object... args) throws Exception {
        for (Method method : obj.getClass().getDeclaredMethods()) {
            if (method.getName().equals(methodName) && method.getParameterCount() == args.length) {
                method.setAccessible(true);
                return method.invoke(obj, args);
            }
        }
        throw new NoSuchMethodException(methodName);
    }

    public static void main(String[] args) throws Exception {
        SecretOperations ops = new SecretOperations();

        // Usage:
        Object result = runPrivateMethod(ops, "square", 7);
        System.out.println("Result: " + result);
    }
}Code language: Java (java)

Explanation:

  • obj.getClass().getDeclaredMethods(): Returns every method on the class, private included, which getMethod() alone would not expose.
  • method.getName().equals(methodName) && method.getParameterCount() == args.length: A simple matching strategy that works well when method names are not overloaded with the same argument count.
  • method.setAccessible(true) followed by method.invoke(obj, args): Bypasses the access check and runs the private method with the supplied arguments.
  • Alternative: For a production-quality version, you would also compare each argument’s runtime type against method.getParameterTypes() to correctly disambiguate overloaded methods that share the same name and argument count.

Exercise 14: Dynamic Array Expansion

Problem Statement: Write a method public static Object growArray(Object array, int newLength) that uses java.lang.reflect.Array to dynamically inspect an existing array’s component type, create a new larger array of that exact type, and copy the old elements over.

Purpose: This exercise helps you practice using the java.lang.reflect.Array utility class, which is needed whenever an array’s element type is only known at runtime rather than at compile time.

Given Input: int[] original = {10, 20, 30};, newLength = 5

Expected Output: [10, 20, 30, 0, 0]

▼ Hint
  • array.getClass().getComponentType() reads the exact element type of the array, working for both primitive types like int and object types alike.
  • Array.newInstance(componentType, newLength) creates a brand new array of that same component type at the requested length.
  • System.arraycopy() copies the original elements into the new array, leaving the extra slots at their default value.
▼ Solution & Explanation
import java.lang.reflect.Array;
import java.util.Arrays;

public class Main {

    public static Object growArray(Object array, int newLength) {
        Class<?> componentType = array.getClass().getComponentType();
        int oldLength = Array.getLength(array);

        Object newArray = Array.newInstance(componentType, newLength);
        System.arraycopy(array, 0, newArray, 0, oldLength);

        return newArray;
    }

    public static void main(String[] args) {
        int[] original = {10, 20, 30};

        // Usage:
        int[] grown = (int[]) growArray(original, 5);
        System.out.println(Arrays.toString(grown));
    }
}Code language: Java (java)

Explanation:

  • array.getClass().getComponentType(): Returns the Class<?> representing the array’s element type, such as int.class for an int[].
  • Array.newInstance(componentType, newLength): Builds a new array reflectively, since new componentType[newLength] is not valid syntax when componentType is only known at runtime.
  • Array.getLength(array) / System.arraycopy(...): Array.getLength() works uniformly on any array type including primitives, then arraycopy() transfers the existing elements into the larger array.
  • Alternative: You could use Arrays.copyOf(original, newLength) directly for a typed array like int[], though java.lang.reflect.Array is required when the array’s type is only known generically at runtime, as in this exercise.

Exercise 15: Generic Type Erasure Inspector

Problem Statement: Create a class field like public List<String> names;. Write a program using field.getGenericType() and ParameterizedType to extract and print the compile-time generic type parameter (String) which is usually erased.

Purpose: This exercise helps you practice reading generic type information that survives compilation, since ordinary reflection on a field’s type alone only reports the raw, erased type.

Expected Output:

Raw type: List
Generic parameter: java.lang.String
▼ Hint
  • field.getType() returns only the raw type, List, with the generic parameter erased at runtime.
  • field.getGenericType() instead returns a Type that still carries the compile-time generic information, as long as it is checked with instanceof ParameterizedType.
  • Cast the result to ParameterizedType and call getActualTypeArguments() to read the actual type used for List<String>, which returns String.class packaged as a Type.
▼ Solution & Explanation
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

public class Main {

    static class Container {
        public List<String> names;
    }

    public static void main(String[] args) throws NoSuchFieldException {
        // Usage:
        Field field = Container.class.getField("names");
        Type genericType = field.getGenericType();

        if (genericType instanceof ParameterizedType parameterizedType) {
            Type[] typeArguments = parameterizedType.getActualTypeArguments();
            System.out.println("Raw type: " + field.getType().getSimpleName());
            System.out.println("Generic parameter: " + typeArguments[0].getTypeName());
        }
    }
}Code language: Java (java)

Explanation:

  • field.getType(): Reports only List, since normal reflection cannot see past type erasure for the raw type alone.
  • field.getGenericType(): Returns richer type information preserved by the compiler in the class file’s signature attribute, which is where generic details survive erasure.
  • parameterizedType.getActualTypeArguments()[0]: Extracts the specific type argument, String, that was used when the field was declared as List<String>.
  • Alternative: You could use field.getGenericType().getTypeName() directly for a one-line summary like java.util.List<java.lang.String>, without breaking it into a raw type and a separate type argument.

Exercise 16: Custom JSON Serializer

Problem Statement: Write a lightweight serializer public String serialize(Object obj). Loop through all fields of an arbitrary object using reflection and format them into a valid JSON string (e.g., {"name": "Alice", "age": 30}).

Purpose: This exercise helps you practice using reflection to build a generic, object-agnostic serializer, the same core technique real JSON libraries rely on internally.

Expected Output: {"name": "Alice", "age": 30}

▼ Hint
  • Loop through getDeclaredFields() and call setAccessible(true) on each one so private fields can be read too.
  • Wrap String values in quotes when appending them, but leave numeric and boolean values unquoted, since that matches valid JSON syntax.
  • Only add a comma separator between fields, not after the very last one, so the resulting string stays valid JSON.
▼ Solution & Explanation
import java.lang.reflect.Field;

public class Main {

    static class UserProfile {
        public String name = "Alice";
        public int age = 30;
    }

    public static String serialize(Object obj) throws IllegalAccessException {
        StringBuilder json = new StringBuilder("{");
        Field[] fields = obj.getClass().getDeclaredFields();

        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            field.setAccessible(true);
            Object value = field.get(obj);

            json.append("\"").append(field.getName()).append("\": ");
            json.append(value instanceof String ? "\"" + value + "\"" : value);

            if (i < fields.length - 1) {
                json.append(", ");
            }
        }

        json.append("}");
        return json.toString();
    }

    public static void main(String[] args) throws IllegalAccessException {
        UserProfile profile = new UserProfile();

        // Usage:
        String json = serialize(profile);
        System.out.println(json);
    }
}Code language: Java (java)

Explanation:

  • obj.getClass().getDeclaredFields(): Collects every field on the object’s class so each one can be serialized in turn.
  • value instanceof String ? "\"" + value + "\"" : value: Quotes string values but leaves other types like int printed as-is, matching JSON’s formatting rules.
  • if (i < fields.length - 1): Adds a comma after every field except the last, avoiding a trailing comma that would make the JSON invalid.
  • Alternative: For a more robust serializer, you would also need to handle nested objects, arrays, and null values explicitly, since this simple version only formats flat fields with primitive or String values correctly.

Exercise 17: Build a Mini-JUnit Framework

Problem Statement: Define a custom @MyTest annotation. Write a test runner program that takes a class name, searches for all methods marked with @MyTest, instantiates the class, and invokes those test methods sequentially.

Purpose: This exercise helps you practice combining a custom runtime annotation with reflection to build a miniature version of the mechanism real testing frameworks use to discover and run test methods.

Expected Output:

Running: testAddition
testAddition passed: true
Running: testSubtraction
testSubtraction passed: true

(the order of the two tests is not guaranteed and may vary between runs)

▼ Hint
  • Declare @MyTest with @Retention(RetentionPolicy.RUNTIME), since annotations are invisible to reflection by default unless their retention policy is explicitly set to RUNTIME.
  • Use method.isAnnotationPresent(MyTest.class) to filter out any method not marked with the annotation, such as a plain helper method.
  • Instantiate the test class once with getDeclaredConstructor().newInstance(), then call method.invoke(instance) for each matching test method.
▼ Solution & Explanation
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface MyTest {
}

class MathTests {
    @MyTest
    public void testAddition() {
        System.out.println("testAddition passed: " + (2 + 2 == 4));
    }

    @MyTest
    public void testSubtraction() {
        System.out.println("testSubtraction passed: " + (5 - 3 == 2));
    }

    public void notATest() {
        System.out.println("This should never run");
    }
}

public class Main {

    public static void runTests(String className) throws Exception {
        Class<?> testClass = Class.forName(className);
        Object instance = testClass.getDeclaredConstructor().newInstance();

        for (Method method : testClass.getDeclaredMethods()) {
            if (method.isAnnotationPresent(MyTest.class)) {
                System.out.println("Running: " + method.getName());
                method.invoke(instance);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        // Usage:
        runTests("MathTests");
    }
}Code language: Java (java)

Explanation:

  • @Retention(RetentionPolicy.RUNTIME): Tells the compiler to keep the annotation in the compiled class file so it can be read by reflection at runtime, unlike the default retention which discards it after compilation.
  • method.isAnnotationPresent(MyTest.class): Checks whether the specific annotation is attached to a given method, letting the runner skip regular helper methods like notATest().
  • method.invoke(instance): Runs each matching test method on the single test class instance created earlier.
  • Alternative: A more complete framework would also create a fresh instance for every test method rather than reusing one instance, matching how JUnit itself isolates test methods from each other by default.

Exercise 18: Dependency Injection (DI) Container

Problem Statement: Create an @Inject annotation. Write a simple container context class that inspects an object’s fields. If a field is marked with @Inject, use reflection to instantiate an object of that field’s type and assign it dynamically.

Purpose: This exercise helps you practice combining an annotation, field inspection, and dynamic instantiation into a tiny dependency injection container, similar in spirit to how larger frameworks wire up objects automatically.

Expected Output: Sending: Welcome aboard!

▼ Hint
  • Declare @Inject with @Target(ElementType.FIELD) and @Retention(RetentionPolicy.RUNTIME), so it can only be applied to fields and remains visible to reflection at runtime.
  • Loop through getDeclaredFields() and check field.isAnnotationPresent(Inject.class) to find fields that need a dependency supplied.
  • For each matching field, call field.getType().getDeclaredConstructor().newInstance() to build a new instance of whatever type the field declares, then assign it with field.set().
▼ Solution & Explanation
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;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Inject {
}

class EmailService {
    public void send(String message) {
        System.out.println("Sending: " + message);
    }
}

class NotificationManager {
    @Inject
    public EmailService emailService;
}

public class Main {

    public static void injectDependencies(Object target) throws Exception {
        for (Field field : target.getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(Inject.class)) {
                Object dependency = field.getType().getDeclaredConstructor().newInstance();
                field.setAccessible(true);
                field.set(target, dependency);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        NotificationManager manager = new NotificationManager();

        // Usage:
        injectDependencies(manager);
        manager.emailService.send("Welcome aboard!");
    }
}Code language: Java (java)

Explanation:

  • field.isAnnotationPresent(Inject.class): Identifies exactly which fields are requesting automatic dependency injection.
  • field.getType().getDeclaredConstructor().newInstance(): Reads the field’s declared type at runtime and builds a fresh instance of that exact type, without the calling code needing to know the type in advance.
  • field.setAccessible(true) followed by field.set(target, dependency): Assigns the newly created dependency into the field, even if the field is private.
  • Alternative: A more capable container would maintain a registry mapping interface types to concrete implementations, rather than always instantiating the field’s exact declared type directly.

Exercise 19: Deep toString() Generator

Problem Statement: Create a utility class that dynamically constructs a toString representation for any object by accessing all its fields, formatted elegantly as ClassName[field1=val1, field2=val2]. Prevent infinite loops if objects reference each other.

Purpose: This exercise helps you practice recursive reflection, walking into nested object fields while tracking already-visited objects to avoid an infinite loop on circular references.

Expected Output: Employee[name=Alice, address=Address[city=Springfield]]

▼ Hint
  • Track visited objects in a Set backed by IdentityHashMap, so equality is checked by reference rather than by equals(), which matters for detecting circular references correctly.
  • Before processing an object’s fields, call visited.add(obj) and check its return value, since add() returns false if the object was already in the set.
  • Recurse into any field whose value is not a simple type like String or a number, so nested objects get their own bracketed representation instead of a raw hash code.
▼ Solution & Explanation
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Set;

public class Main {

    public static String toString(Object obj) throws IllegalAccessException {
        return toString(obj, Collections.newSetFromMap(new IdentityHashMap<>()));
    }

    private static String toString(Object obj, Set<Object> visited) throws IllegalAccessException {
        if (obj == null) {
            return "null";
        }
        if (!visited.add(obj)) {
            return obj.getClass().getSimpleName() + "[already visited]";
        }

        StringBuilder result = new StringBuilder(obj.getClass().getSimpleName()).append("[");
        Field[] fields = obj.getClass().getDeclaredFields();

        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            field.setAccessible(true);
            Object value = field.get(obj);

            String formattedValue = isSimpleType(value) ? String.valueOf(value) : toString(value, visited);
            result.append(field.getName()).append("=").append(formattedValue);

            if (i < fields.length - 1) {
                result.append(", ");
            }
        }

        result.append("]");
        return result.toString();
    }

    private static boolean isSimpleType(Object value) {
        return value == null || value instanceof String || value instanceof Number
                || value instanceof Boolean || value instanceof Character;
    }

    static class Address {
        public String city = "Springfield";
    }

    static class Employee {
        public String name = "Alice";
        public Address address = new Address();
    }

    public static void main(String[] args) throws IllegalAccessException {
        Employee employee = new Employee();

        // Usage:
        System.out.println(toString(employee));
    }
}Code language: Java (java)

Explanation:

  • Collections.newSetFromMap(new IdentityHashMap<>()): Builds a Set that compares elements using == instead of equals(), which is important here since two unrelated objects could otherwise compare as equal.
  • visited.add(obj) returning false: Signals the object has already been visited in this call chain, so the method returns early with an [already visited] placeholder instead of recursing forever.
  • isSimpleType(value) ? String.valueOf(value) : toString(value, visited): Decides whether to print a value directly or recurse into it, which is what produces the nested Address[city=Springfield] representation.
  • Alternative: You could rely on Object‘s default identity-based equals() and hashCode() with a regular HashSet instead of IdentityHashMap, though a custom equals() override on any visited class could break that detection, making IdentityHashMap the safer choice.

Exercise 20: Command-Line Route Dispatcher

Problem Statement: Create an application where commands are mapped directly to methods via annotations (e.g., @Command(name="login")). Write a routing engine that captures a CLI string argument, finds the matching annotated method at runtime, and runs it with passed parameters.

Purpose: This exercise helps you practice using an annotation element, name(), as a lookup key at runtime, a pattern used by many routing and command-dispatch frameworks.

Given Input: command "login", argument "alice"

Expected Output: Logging in as: alice

▼ Hint
  • Declare @Command as a method-level annotation with a String name() element, so each handler method can specify which CLI command it responds to.
  • Loop through getDeclaredMethods() and read each method’s Command annotation with method.getAnnotation(Command.class), which returns null for methods that don’t have it.
  • Compare command.name() against the requested command string, and call method.invoke(handlers, args) on the first match found.
▼ Solution & Explanation
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Command {
    String name();
}

class CliHandlers {
    @Command(name = "login")
    public void login(String username) {
        System.out.println("Logging in as: " + username);
    }

    @Command(name = "logout")
    public void logout() {
        System.out.println("Logging out");
    }
}

public class Main {

    public static void dispatch(Object handlers, String commandName, Object... args) throws Exception {
        for (Method method : handlers.getClass().getDeclaredMethods()) {
            Command command = method.getAnnotation(Command.class);
            if (command != null && command.name().equals(commandName)) {
                method.invoke(handlers, args);
                return;
            }
        }
        System.out.println("Unknown command: " + commandName);
    }

    public static void main(String[] args) throws Exception {
        CliHandlers handlers = new CliHandlers();

        // Usage:
        dispatch(handlers, "login", "alice");
    }
}Code language: Java (java)

Explanation:

  • method.getAnnotation(Command.class): Retrieves the Command annotation instance attached to a method, or null if the annotation is not present.
  • command.name().equals(commandName): Matches the annotation’s declared name against the command the caller wants to run, rather than matching on the method’s own Java name.
  • method.invoke(handlers, args): Calls the matching method with whatever arguments were supplied, using Java’s varargs to forward them directly.
  • Alternative: You could build a Map<String, Method> once by scanning all annotated methods up front, then look up commands by name directly, avoiding a full method scan on every single dispatch call.

Filed Under: Java Exercises

Did you find this page helpful? Let others know about it. Sharing helps me continue to create free Python resources.

TweetF  sharein  shareP  Pin

About Vishal

I’m Vishal Hule, the Founder of PYnative.com. As a Python developer, I enjoy assisting students, developers, and learners. Follow me on Twitter.

Related Tutorial Topics:

Java Exercises

All Coding Exercises:

C Exercises
C++ Exercises
Python Exercises
Java Exercises
C# Exercises

Python Exercises and Quizzes

Free coding exercises and quizzes cover Python basics, data structure, data analytics, and more.

  • 15+ Topic-specific Exercises and Quizzes
  • Each Exercise contains 25+ questions
  • Each Quiz contains 25 MCQ
Exercises
Quizzes

Leave a Reply Cancel reply

your email address will NOT be published. all comments are moderated according to our comment policy.

Use <pre> tag for posting code. E.g. <pre> Your entire code </pre>

In: Java Exercises
TweetF  sharein  shareP  Pin

  Java Exercises

  • All Java Exercises
  • Java Exercise for Beginners
  • Java Loops Exercise
  • Java String Exercise
  • Java ArrayList Exercise
  • Java LinkedList Exercise
  • Java HashMap and TreeMap Exercise
  • Java HashSet and TreeSet Exercise
  • Java OOP Exercise
  • Java Methods Exercise
  • Java Enums Exercise
  • Java Exception Handling Exercise
  • Java File Handling Exercise
  • Java Date and Time Exercise
  • Java Data Structures Exercise
  • Java Sorting and Searching Exercise
  • Java Lambda and Functional Interfaces Exercise
  • Java Regex Exercise
  • Java Random Data Generation Exercise
  • Java Generics Exercise
  • Java Reflection Exercise
  • Java JDBC Exercise

All Coding Exercises

Python Exercises C Exercises C++ Exercises Java Exercises C# Exercises

About PYnative

PYnative.com is for Python lovers. Here, You can get Tutorials, Exercises, and Quizzes to practice and improve your Python skills.

Follow Us

To get New Python Tutorials, Exercises, and Quizzes

  • Twitter
  • Facebook
  • Sitemap

Explore Python

  • Learn Python
  • Python Basics
  • Python Databases
  • Python Exercises
  • Python Quizzes
  • Online Python Code Editor
  • Python Tricks

Coding Exercises

  • C Exercises
  • C++ Exercises
  • Python Exercises
  • Java Exercises
  • C# Exercises

Legal Stuff

  • About Us
  • Contact Us

We use cookies to improve your experience. While using PYnative, you agree to have read and accepted our:

  • Terms Of Use
  • Privacy Policy
  • Cookie Policy

Copyright © 2018–2026 pynative.com