Back to Reflection & Object Creation
Q2Question 2 of 2

What is the Reflection API?

Key Concepts
  • Reflection lets you inspect and modify classes, methods, fields, and constructors at runtime
  • You can create objects, invoke methods, and access private fields dynamically
  • Used by frameworks: Spring (DI), JPA (entity mapping), JUnit (test discovery), Jackson (JSON binding)
  • Slower than direct access (~10x) and bypasses compile-time type checking
  • Java modules (Java 9+) restrict reflection via --add-opens

Definition

Reflection lets you inspect and manipulate classes, fields, and methods at runtime without knowing them at compile time. Frameworks like Spring and JPA depend on it.

Reflection capabilities:

  • Inspect: Get class name, fields, methods, annotations, constructors at runtime.
  • Create: Instantiate objects by class name without knowing the type at compile time.
  • Invoke: Call methods dynamically, including private ones.
  • Access: Read and write field values, even private fields via setAccessible(true).

Reflection is like a bank auditor who can open any drawer (private field), read any form (method), and start any procedure (invoke) without needing pre-arranged access. Powerful but risky – it bypasses normal security checks.

Understanding the Concepts

How does reflection work?

JAVA
import java.lang.reflect.*;

// Get the class
Class<?> clazz = Class.forName("com.bank.core.Account");

// Inspect fields
for (Field field : clazz.getDeclaredFields()) {
    System.out.println(field.getName() + " : " + field.getType().getSimpleName());
}

// Create an instance
Constructor<?> ctor = clazz.getDeclaredConstructor(String.class, BigDecimal.class);
Object account = ctor.newInstance("SAV2001", new BigDecimal("500000"));

// Invoke a method
Method getBalance = clazz.getMethod("getBalance");
Object balance = getBalance.invoke(account);
System.out.println("Balance: " + balance);

// Access a private field
Field balanceField = clazz.getDeclaredField("balance");
balanceField.setAccessible(true);  // bypass private
BigDecimal val = (BigDecimal) balanceField.get(account);

Who uses reflection?

  • Spring: Creates beans, injects dependencies, processes @Autowired/@Value.
  • JPA/Hibernate: Maps entity fields to database columns.
  • Jackson/Gson: Converts objects to/from JSON by reading fields.
  • JUnit: Discovers and invokes @Test methods.

What are the downsides?

  • Slow: ~10x slower than direct method calls.
  • No compile-time safety: Errors appear at runtime (NoSuchMethodException, ClassNotFoundException).
  • Breaks encapsulation: setAccessible(true) accesses private fields.
  • Module restrictions: Java 9+ modules block deep reflection unless --add-opens is specified.

What should a senior engineer know?

  • MethodHandle (Java 7+) is a faster alternative for dynamic method invocation.
  • VarHandle (Java 9+) provides memory fence operations.
  • GraalVM native-image requires reflection configuration because it compiles ahead-of-time and cannot discover classes at runtime.
  • Spring Native generates reflection metadata automatically for native compilation.

What are common mistakes?

A common mistake is not understanding the concept fully before using it in production banking applications.

Wrong approach:

JAVA
package com.bank.service;

public class TransferService {
    public void transfer(String fromAccount, String toAccount, double amount) {
        // WRONG: using double for money
        double balance = getBalance(fromAccount);
        balance = balance - amount;
    }
    private double getBalance(String id) { return 10000.0; }
}

Correct approach:

JAVA
package com.bank.service;

import java.math.BigDecimal;

public class TransferService {
    public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
        // CORRECT: using BigDecimal for money
        BigDecimal balance = getBalance(fromAccount);
        balance = balance.subtract(amount);
    }
    private BigDecimal getBalance(String id) { return new BigDecimal("10000.00"); }
}

Always use BigDecimal for monetary calculations in banking systems to avoid floating-point precision errors.

What are the edge cases?

Edge cases can cause unexpected behavior in banking applications. Here are some to watch for.

JAVA
package com.bank.edge;

public class EdgeCaseDemo {
    public static void main(String[] args) {
        // Edge case 1: null handling
        String accountId = null;
        try {
            System.out.println(accountId.length());
        } catch (NullPointerException e) {
            System.out.println("Null account ID");
        }

        // Edge case 2: empty vs null
        String emptyId = "";
        System.out.println(emptyId.isEmpty());  // true
        System.out.println(emptyId == null);     // false

        // Edge case 3: boundary values
        java.math.BigDecimal zero = java.math.BigDecimal.ZERO;
        java.math.BigDecimal negativeBalance = new java.math.BigDecimal("-0.01");
        System.out.println(zero.compareTo(negativeBalance) > 0);  // true
    }
}
TEXT
Null account ID
true
false
true

Always test with null values, empty strings, boundary values, and negative amounts in banking applications.

What are common interview follow-up questions?

Interviewers often follow up with these related questions:

  • How does this concept apply to a high-throughput banking transaction system?
  • What are the performance implications in a production environment?
  • How would you debug issues related to this concept?
  • What tools would you use to monitor this in production?
  • How does this behave differently in Java 8 vs Java 17+?

When answering follow-up questions, always relate your answer back to practical experience. For example, at National Bank of India, Rajesh debugged a production issue related to this concept by using JFR recordings and heap dumps. Providing concrete examples from banking systems shows the interviewer that you understand both the theory and the practice.

Visual Overview

DIAGRAM
  Reflection API:
    Class.forName("Account")     → get Class object
    clazz.getDeclaredFields()    → inspect fields
    clazz.getMethod("getBalance") → get Method object
    method.invoke(account)       → call the method
    field.setAccessible(true)    → bypass private
    ctor.newInstance(args)       → create object

  Used by: Spring, JPA, Jackson, JUnit
  Downside: slow, no compile-time safety, breaks encapsulation

Key Takeaway

The Reflection API lets you inspect and manipulate classes, fields, methods, and constructors at runtime. You can create objects, invoke methods, and access private fields dynamically without knowing the types at compile time.

Frameworks like Spring, JPA, Jackson, and JUnit depend heavily on reflection. But it is slower than direct access, bypasses compile-time type checking, and can break encapsulation. Java modules restrict deep reflection for security.

One sentence to remember: reflection inspects and manipulates classes at runtime – frameworks depend on it, but it is slow and bypasses safety.

---

Share
Use ← → arrow keys to navigate