DEV Community

Cover image for How Java Reflection Powers Spring's Dependency Injection
Md. Monowarul Amin
Md. Monowarul Amin

Posted on

How Java Reflection Powers Spring's Dependency Injection

How Java Reflection Powers Spring's Dependency Injection

You write @Autowired. Spring wires everything. Ever wondered how?

The answer is Reflection — one of Java's most powerful and misunderstood features.


What is Reflection?

Normally, Java is a compile-time language — you must know your classes, methods, and fields before the program runs. Reflection breaks that rule.

It lets you inspect and manipulate any class at runtime — read its fields, invoke its methods, and instantiate it — even if you never imported it.

// Normal way — you must know the class at compile time
OrderService s = new OrderService();

// Reflection way — class name can come from anywhere at runtime
Class<?> clazz = Class.forName("com.myapp.OrderService");
Object s = clazz.getDeclaredConstructor().newInstance();
Enter fullscreen mode Exit fullscreen mode

Think of it like this:

🔦 Normal Java is like reading a book you already own.

🔍 Reflection is like walking into an unknown library, picking any book off the shelf, and reading it on the spot.


Why Does Spring Need It?

Spring is a framework — a library compiled and shipped before your application even exists. It has never seen your OrderService, PaymentService, or UserRepository. Yet at runtime, it finds them, creates them, and wires them together automatically.

The only tool in Java that makes this possible is Reflection.

If Spring used new instead, its internal code would have to look like:

// Spring would have to hardcode YOUR classes — impossible!
if (type == OrderService.class) return new OrderService();
if (type == PaymentService.class) return new StripePaymentService();
// ... every class in your app listed here
Enter fullscreen mode Exit fullscreen mode

Spring is compiled years before your app is written. It cannot new your classes. So it uses reflection to discover and instantiate them at runtime.


How Spring Uses Reflection — Step by Step

Let's say you write this:

@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;
}
Enter fullscreen mode Exit fullscreen mode

Here's what Spring does internally:

Step 1 — Classpath Scanning

Spring scans your packages for @Service, @Component, @Repository using reflection:

// Finds all classes annotated with @Service at runtime
Set<Class<?>> beans = reflections.getTypesAnnotatedWith(Service.class);
// → finds OrderService, PaymentService, etc.
Enter fullscreen mode Exit fullscreen mode

Step 2 — Instantiation Without Knowing the Class

Class<?> clazz = Class.forName("com.myapp.OrderService");
Object instance = clazz.getDeclaredConstructor().newInstance();
// Spring just created YOUR class without ever writing `new OrderService()`
Enter fullscreen mode Exit fullscreen mode

Step 3 — Reading @Autowired and Injecting

Field[] fields = OrderService.class.getDeclaredFields();
for (Field field : fields) {
    if (field.isAnnotationPresent(Autowired.class)) {
        field.setAccessible(true);  // bypasses private modifier!
        Object dependency = applicationContext.getBean(field.getType());
        field.set(instance, dependency);  // injects PaymentService
    }
}
Enter fullscreen mode Exit fullscreen mode

This is how Spring injects into private fields — something that's normally forbidden in Java. Only reflection can do it.


The ApplicationContext — Spring's Bean Registry

ApplicationContext is Spring's IoC container. Think of it as a warehouse that stores all your objects (beans).

🏭 Analogy: Spring's ApplicationContext is like an Amazon warehouse.

At startup, all items (beans) are stocked. When you need something, you don't manufacture it yourself — you just request it and it's handed to you instantly.

Internally it works like a map:

ApplicationContext Registry
│
├── beansByName
│     ├── "orderService"          → OrderService instance
│     ├── "stripePaymentService"  → StripePaymentService instance
│     └── "paypalPaymentService"  → PaypalPaymentService instance
│
└── beansByType
      ├── PaymentService.class    → ["stripePaymentService", "paypalPaymentService"]
      └── OrderService.class      → ["orderService"]
Enter fullscreen mode Exit fullscreen mode

When Spring wires dependencies, it calls:

applicationContext.getBean(PaymentService.class);
// → looks up PaymentService in the registry
// → returns the already-created instance
Enter fullscreen mode Exit fullscreen mode

It does not create a new object every time. The instance was already built at startup and is simply retrieved.


What If Multiple Classes Implement the Same Interface?

@Service public class StripePaymentService implements PaymentService { ... }
@Service public class PaypalPaymentService implements PaymentService  { ... }
Enter fullscreen mode Exit fullscreen mode

Now getBean(PaymentService.class) finds two candidates and throws:

NoUniqueBeanDefinitionException: expected single bean but found 2:
stripePaymentService, paypalPaymentService
Enter fullscreen mode Exit fullscreen mode

Spring refuses to guess. You resolve it by:

Solution How
@Primary Mark one as the default winner
@Qualifier("name") Explicitly name which one to inject
List<PaymentService> Inject all implementations as a list
Map<String, PaymentService> Inject all, keyed by bean name
// Inject all and pick at runtime — most flexible
@Autowired
private Map<String, PaymentService> paymentServices;

paymentServices.get("stripePaymentService").charge(amount); // dynamic!
Enter fullscreen mode Exit fullscreen mode

Why Not Just Use new?

new Reflection + DI
Class must be known at compile time ✅ Yes ❌ No — runtime
Can inject into private fields ❌ No ✅ Yes
Can scan annotations automatically ❌ No ✅ Yes
Works with any class, even unknown ones ❌ No ✅ Yes
Supports AOP, @Transactional, proxies ❌ No ✅ Yes

Without reflection, Spring would not be a framework.

It would just be a very long main() method calling new on everything.


Quick Recap

  • Reflection lets Java inspect and instantiate classes at runtime without knowing them at compile time.
  • Spring uses reflection to scan annotations, create beans, and inject dependencies automatically.
  • ApplicationContext is the registry that stores all beans — wired once at startup, retrieved on demand.
  • Multiple implementations of an interface are all stored; you must tell Spring which to use via @Primary, @Qualifier, or by injecting a List/Map.
  • new cannot replace reflection because Spring is compiled before your classes exist — only reflection bridges that gap.

Understanding reflection turns Spring from magic into mechanics — and that's when you go from using a framework to truly mastering it.

Top comments (0)