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.Arrayfor 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()andgetSimpleName()read the loaded class’s metadata without needing a compile-time reference to the class.getSuperclass()returns aClass<?>object for the immediate parent class, so callinggetName()on that gives its fully qualified name.
▼ Solution & Explanation
Explanation:
Class.forName(className): Looks up and loads the class matching the given fully qualified name, throwingClassNotFoundExceptionif 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 aClass<?>object, then reads its fully qualified name.- Alternative: You could catch
ClassNotFoundExceptionlocally insideinspectClass()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 anintwhere each bit represents a different modifier, such as public, abstract, or final.- The
Modifierutility class provides static methods likeisPublic(),isAbstract(), andisFinal()that decode individual bits from thatint. - Use
isInterface()directly on theClass<?>object to check for the interface modifier, sinceModifieritself does not expose a dedicated method for it.
▼ Solution & Explanation
Explanation:
clazz.getModifiers(): Returns a bitmaskintencoding 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 onClass<?>itself, since checking for the interface modifier throughModifierwould require comparing againstModifier.INTERFACEdirectly.- 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, callinggetInterfaces()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
Explanation:
obj.getClass(): Returns the exact runtime class of the object, which matters ifobjwas 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 toObject.- Alternative: You could use a
Set<String>instead of aList<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, unlikegetFields(), 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
Explanation:
clazz.getDeclaredFields(): Returns an array ofFieldobjects for every field declared in the class itself, regardless of access level.field.getType(): Returns aClass<?>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 theInterfaceTrackerexercise.
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 specificFieldobject, 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 sinceField.get()returnsObject.
▼ Solution & Explanation
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 thisFieldobject, allowingget()to succeed despite the field being private.(String) field.get(target): Retrieves the field’s current value from the specific target instance, cast fromObjectback toString.- Alternative: You could catch the checked exceptions inside
readPrivateField()and rethrow an uncheckedRuntimeExceptioninstead, 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
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 thisFieldobject, so the followingset()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
valueis assignment-compatible withfield.getType()before callingset(), so a mismatched type throws a clearer custom error instead of anIllegalArgumentExceptionfrom 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 checkModifier.isStatic(field.getModifiers())to skip instance fields entirely. - Use
Map.class.isAssignableFrom(field.getType())andList.class.isAssignableFrom(field.getType())to detect fields whose declared type is aMapor aListimplementation. - Pass
nulltofield.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
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 isMapor one of its subtypes, correctly matching implementations likeHashMaptoo.field.get(null): Reads a static field’s current value, passingnullsince static fields are not tied to any specific instance.- Alternative: You could restrict the type check to exact matches with
field.getType() == Map.class, butisAssignableFrom()is more useful here since it also matches concrete implementations likeHashMaporArrayList.
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, andInvocationTargetExceptiontogether with a multi-catch block.
▼ Solution & Explanation
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, sincegetConstructor()only finds public constructors.- Call
setAccessible(true)on theConstructorobject 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
Explanation:
Singleton.class.getDeclaredConstructor(): Finds the private no-argument constructor, whichgetConstructor()alone cannot see since that method only returns public constructors.constructor.setAccessible(true): Suppresses the access control check, allowingnewInstance()to call the private constructor from outside the class.first == second: Compares object references rather than field values, revealing that a second, distinctSingletoninstance was created.- Alternative: You could defend against this specific attack by throwing an exception inside the constructor if
INSTANCEis 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 ofClass<?>objects describing the exact parameter types of the constructor you want to find.- Pass
String.classandint.class, in that order, to match theUser(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
Explanation:
User.class.getDeclaredConstructor(String.class, int.class): Searches for a constructor whose parameter types exactly matchStringandint, in that order, distinguishing it from the single-argumentUser(String name)constructor.int.class: Refers to the primitive type token, which must be used here rather thanInteger.class, since the constructor parameter itself is declared as a primitiveint.twoArgConstructor.newInstance("Alice", 30): Invokes the located constructor with the given arguments, autoboxing 30 into the primitiveintparameter as needed.- Alternative: You could use
getDeclaredConstructors()to list every constructor on the class and manually inspect each one’sgetParameterTypes(), thoughgetDeclaredConstructor()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 togetDeclaredFields()for fields.method.getReturnType().getSimpleName()reads the method’s return type, andmethod.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
Explanation:
clazz.getDeclaredMethods(): Returns an array ofMethodobjects for every method declared in the class itself, regardless of access level.method.getParameterTypes(): Returns aClass<?>[]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 ofgetParameterTypes()if you also needed to preserve generic type information likeList<String>rather than justList.
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.classtwice, matching the twointparameters declared onadd(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
Explanation:
Calculator.class.getMethod("add", int.class, int.class): Finds the publicaddmethod whose parameters are exactly twoints.addMethod.invoke(calculator, 10, 5): Calls that method on thecalculatorinstance, passing 10 and 5 as arguments, and autoboxing the returnedintinto anInteger.Object result:invoke()always returnsObject, since reflection has no compile-time knowledge of the method’s actual return type.- Alternative: You could use
getDeclaredMethod()instead ofgetMethod()ifadd()were private or package-private, sincegetMethod()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 callinggetMethod()directly, since private methods are invisible togetMethod()but visible togetDeclaredMethods(). - 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 matchingMethodbefore invoking it, the same bypass used earlier for private fields and constructors.
▼ Solution & Explanation
Explanation:
obj.getClass().getDeclaredMethods(): Returns every method on the class, private included, whichgetMethod()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 bymethod.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 likeintand 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
Explanation:
array.getClass().getComponentType(): Returns theClass<?>representing the array’s element type, such asint.classfor anint[].Array.newInstance(componentType, newLength): Builds a new array reflectively, sincenew componentType[newLength]is not valid syntax whencomponentTypeis only known at runtime.Array.getLength(array)/System.arraycopy(...):Array.getLength()works uniformly on any array type including primitives, thenarraycopy()transfers the existing elements into the larger array.- Alternative: You could use
Arrays.copyOf(original, newLength)directly for a typed array likeint[], thoughjava.lang.reflect.Arrayis 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 aTypethat still carries the compile-time generic information, as long as it is checked withinstanceof ParameterizedType.- Cast the result to
ParameterizedTypeand callgetActualTypeArguments()to read the actual type used forList<String>, which returnsString.classpackaged as aType.
▼ Solution & Explanation
Explanation:
field.getType(): Reports onlyList, 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 asList<String>.- Alternative: You could use
field.getGenericType().getTypeName()directly for a one-line summary likejava.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 callsetAccessible(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
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 likeintprinted 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
@MyTestwith@Retention(RetentionPolicy.RUNTIME), since annotations are invisible to reflection by default unless their retention policy is explicitly set toRUNTIME. - 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 callmethod.invoke(instance)for each matching test method.
▼ Solution & Explanation
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 likenotATest().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
@Injectwith@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 checkfield.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 withfield.set().
▼ Solution & Explanation
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 byfield.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
Setbacked byIdentityHashMap, so equality is checked by reference rather than byequals(), which matters for detecting circular references correctly. - Before processing an object’s fields, call
visited.add(obj)and check its return value, sinceadd()returnsfalseif the object was already in the set. - Recurse into any field whose value is not a simple type like
Stringor a number, so nested objects get their own bracketed representation instead of a raw hash code.
▼ Solution & Explanation
Explanation:
Collections.newSetFromMap(new IdentityHashMap<>()): Builds aSetthat compares elements using==instead ofequals(), which is important here since two unrelated objects could otherwise compare as equal.visited.add(obj)returningfalse: 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 nestedAddress[city=Springfield]representation.- Alternative: You could rely on
Object‘s default identity-basedequals()andhashCode()with a regularHashSetinstead ofIdentityHashMap, though a customequals()override on any visited class could break that detection, makingIdentityHashMapthe 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
@Commandas a method-level annotation with aString name()element, so each handler method can specify which CLI command it responds to. - Loop through
getDeclaredMethods()and read each method’sCommandannotation withmethod.getAnnotation(Command.class), which returnsnullfor methods that don’t have it. - Compare
command.name()against the requested command string, and callmethod.invoke(handlers, args)on the first match found.
▼ Solution & Explanation
Explanation:
method.getAnnotation(Command.class): Retrieves theCommandannotation instance attached to a method, ornullif 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.

Leave a Reply