# Spring Core — IoC Container, Dependency Injection, Beans and Scopes

Source: https://skills101.in/learn/spring-boot/spring-core-dependency-injection — Skills101 free tutorial (Advanced Java & Spring Boot). Updated 2026-11-09.

Before Spring Boot, learn the one idea that Spring is built on: **you don't create your objects — the container does.**

## 1. The problem

```java
// Not compiled: the tightly-coupled version we want to avoid
class WelcomeService {
    private EmailNotifier notifier = new EmailNotifier();   // hard-wired
}
```

`WelcomeService` is now glued to `EmailNotifier`. To send SMS instead, or to test without sending real emails, you must edit the class. That is **tight coupling**.

## 2. Inversion of Control (IoC) and Dependency Injection (DI)

Instead, the class **declares** what it needs (usually as constructor parameters), and the **Spring IoC container** creates the objects and passes them in. Control of object creation is "inverted" from your code to the framework.

![The Spring IoC container creates EmailNotifier and SmsNotifier beans and injects them into WelcomeService through its constructor](https://skills101.in/learn-img/spring-boot/ioc.svg)

Objects managed by the container are called **beans**.

## 3. A complete example

An interface and two implementations:

```java
// File: course-api/src/main/java/com/skills101/demos/di/Notifier.java
package com.skills101.demos.di;

public interface Notifier {
    String send(String to, String message);
}
```

```java
// File: course-api/src/main/java/com/skills101/demos/di/EmailNotifier.java
package com.skills101.demos.di;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
@Primary   // the default choice when someone asks for "a Notifier"
public class EmailNotifier implements Notifier {

    @Override
    public String send(String to, String message) {
        return "EMAIL to " + to + ": " + message;
    }
}
```

```java
// File: course-api/src/main/java/com/skills101/demos/di/SmsNotifier.java
package com.skills101.demos.di;

import org.springframework.stereotype.Component;

@Component("sms")   // bean name "sms"
public class SmsNotifier implements Notifier {

    @Override
    public String send(String to, String message) {
        return "SMS to " + to + ": " + message;
    }
}
```

The class that needs them — note there is no `new` anywhere:

```java
// File: course-api/src/main/java/com/skills101/demos/di/WelcomeService.java
package com.skills101.demos.di;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class WelcomeService {

    private final Notifier main;
    private final Notifier backup;

    // Spring calls this constructor and passes the beans in — we never write "new EmailNotifier()"
    public WelcomeService(Notifier main, @Qualifier("sms") Notifier backup) {
        this.main = main;
        this.backup = backup;
    }

    public void welcome(String student) {
        System.out.println(main.send(student, "Welcome to Skills101!"));
        System.out.println(backup.send(student, "Your login OTP is 4821"));
    }
}
```

Start a container and use it:

```java
// File: course-api/src/main/java/com/skills101/demos/di/DiDemo.java
package com.skills101.demos.di;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

public class DiDemo {

    @Configuration
    @ComponentScan   // find @Component / @Service classes in this package
    static class AppConfig {
    }

    public static void main(String[] args) {
        try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {   // the IoC container
            WelcomeService service = context.getBean(WelcomeService.class);
            service.welcome("Asha");
            // beans are singletons by default: the container hands out the same object every time
            System.out.println("Same object? " + (service == context.getBean(WelcomeService.class)));
            System.out.println("Notifiers: " + context.getBeanNamesForType(Notifier.class).length);
        }
    }
}
```

<!-- expect: course-api/di.txt -->
```text
EMAIL to Asha: Welcome to Skills101!
SMS to Asha: Your login OTP is 4821
Same object? true
Notifiers: 2
```

- `@Component` marks a class as a bean. `@Service`, `@Repository` and `@Controller` are `@Component` with a clearer meaning.
- There are two `Notifier` beans, so Spring needs a rule: `@Primary` picks the default, `@Qualifier("sms")` asks for a specific one by name.
- **Same object? true** — beans are **singletons** by default: one shared instance per container.

## 4. Three ways to inject

| Style | Example | Use? |
|---|---|---|
| Constructor | `public WelcomeService(Notifier n)` | **Yes** — fields can be `final`, easy to test, fails fast if missing |
| Setter | `@Autowired void setNotifier(Notifier n)` | for optional dependencies |
| Field | `@Autowired private Notifier n;` | avoid — hidden, hard to test |

With a single constructor, `@Autowired` isn't even needed (as in our example).

## 5. @Bean methods

For classes you can't annotate (from a library), create beans in a `@Configuration` class:

```java
// Not compiled: a short sketch — the real version is SecurityConfig in lesson 9
@Configuration
class AppConfig {
    @Bean
    PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
}
```

We use exactly this pattern for security in a later lesson.

## 6. Scopes and lifecycle

| Scope | Instances |
|---|---|
| `singleton` (default) | one per container |
| `prototype` | a new one every time it's requested |
| `request` / `session` | one per HTTP request / user session (web apps) |

Lifecycle: create → inject dependencies → `@PostConstruct` method → in use → `@PreDestroy` method when the container closes. Because singletons are shared across threads, **don't keep per-user data in fields** of a singleton bean.

## Common mistakes

- `NoUniqueBeanDefinitionException`: two beans of the same type and no `@Primary`/`@Qualifier`.
- `NoSuchBeanDefinitionException`: the class isn't annotated, or is outside the scanned package.
- Calling `new MyService()` yourself — that object isn't a bean, so nothing gets injected into it.

## Quick recap

- IoC: the container creates objects; DI: it passes their dependencies in.
- `@Component` / `@Service` / `@Repository` / `@Controller` declare beans; `@Bean` methods too.
- Prefer constructor injection; use `@Primary` / `@Qualifier` when several beans fit.
- Default scope is singleton.


Practice quiz and free certificate: https://skills101.in/learn/spring-boot/spring-core-dependency-injection