On this page

Spring IoC Container & Beans

What is the Spring IoC container?

In the previous page, we saw that manually creating and connecting objects gets hard as the app grows. The Spring IoC container does this job for you.

It's the part of Spring that:

  • creates your objects,
  • connects them by giving each one its dependencies,
  • keeps them ready for use,
  • and cleans them up when the app stops.

In Spring, the container is called the ApplicationContext.

Instead of you writing all the new calls in main, you just ask the container for what you need:

ApplicationContext context = SpringApplication.run(ShopApplication.class, args);
OrderService orderService = context.getBean(OrderService.class);
orderService.placeOrder("asha@example.com", "Laptop");

Spring already created OrderService and everything it needs. This is Inversion of Control in action: Spring controls object creation, not your code.

What is a Spring Bean?

A Spring Bean is simply an object that the Spring container creates and manages.

text
1new OrderService(...) → a normal object (you manage it)
2OrderService created by Spring → a bean (Spring manages it)

The class is the same. The difference is who created it.

By default, Spring creates one object per bean and shares it everywhere it's needed. So if five services need NotificationService, they all get the same object. This is called singleton scope, and it solves the "sharing is manual" problem from the previous page.

How Spring creates, manages and connects objects

When your app starts, Spring goes through these steps:

  1. Scan your packages for classes marked as beans (with annotations like @Service).
  2. Read each class's constructor to see what it needs.
  3. Create the beans in the right order: dependencies first.
  4. Inject each dependency into the classes that need it.
  5. Store all beans in the container, ready to use.
  6. Clean up the beans when the app shuts down.

Spring figures out the order on its own. You never write the wiring code.

But how does Spring know which classes should be beans? You tell it with annotations.

@Component, @Service and @Autowired

@Component

@Component marks a class as a bean: "Spring, please create and manage this."

@Component
public class EmailNotificationService implements NotificationService {
@Override
public void send(String to, String message) {
System.out.println("Email to " + to + ": " + message);
}
}

@Service

@Service works exactly like @Component. It just tells readers that this class contains business logic.

@Service
public class OrderService {
private final NotificationService notificationService;
public OrderService(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void placeOrder(String customer, String product) {
System.out.println("Order placed: " + product);
notificationService.send(customer, "Your order for " + product + " is confirmed");
}
}

Spring has a few of these "labels", all of which create beans:

AnnotationUse it for
@ComponentAny general class
@ServiceBusiness logic
@RepositoryDatabase access
@Controller / @RestControllerHandling web requests

@Autowired

@Autowired tells Spring: "find a matching bean and inject it here."

The problem it solves: before annotations, Spring needed you to write every connection by hand in an XML file:

xml
1<bean id="orderService" class="com.example.shop.OrderService">
2 <constructor-arg ref="emailNotificationService"/>
3</bean>

With hundreds of classes, that meant hundreds of lines to write and update. @Autowired removes all of it. Spring looks at what's needed, finds a bean of that type, and connects it automatically. That's what "auto-wire" means.

@Service
public class OrderService {
private final NotificationService notificationService;
@Autowired
public OrderService(NotificationService notificationService) {
this.notificationService = notificationService;
}
}

Spring sees that the constructor needs a NotificationService, finds EmailNotificationService, and passes it in.

When can you skip it? If a class has only one constructor, Spring uses it automatically, so @Autowired isn't needed. That's why our OrderService earlier worked without it.

When do you need it?

SituationNeed @Autowired?
Only one constructorNo
More than one constructorYes, on the one Spring should use
Setter injectionYes, on the setter
Field injectionYes, on the field

Why not put it on the final field instead? @Autowired on a field means field injection: Spring fills the field after the constructor runs. But Java requires a final field to be set inside the constructor, so the two can't work together. To keep the benefits of constructor injection, keep the field final and let the constructor receive the dependency.

@Configuration and @Bean

@Component and @Service only work on classes you wrote, because you have to add the annotation to the class.

But what if you need a bean from a library, like a mail client, where you can't edit the class? Or an object that needs special setup?

You create it yourself in a @Configuration class, using a @Bean method:

@Configuration
public class MailConfig {
@Bean
public SmtpClient smtpClient() {
return new SmtpClient("smtp.example.com", 587);
}
}
  • @Configuration says: "this class defines beans."
  • @Bean says: "the object returned by this method is a bean."

Spring calls the method once and manages the returned object like any other bean. Other classes can now receive SmtpClient through their constructors.

A @Bean method can also receive other beans as parameters:

@Bean
public NotificationService notificationService(SmtpClient smtpClient) {
return new EmailNotificationService(smtpClient);
}
@Component / @Service@Configuration + @Bean
Where it goesOn your own classOn a method that returns an object
Who creates the objectSpringYour method
Best forYour own classesLibrary classes or custom setup

@Primary and @Qualifier

What if there are two beans of the same type?

@Component
public class EmailNotificationService implements NotificationService { ... }
@Component
public class SmsNotificationService implements NotificationService { ... }

OrderService asks for a NotificationService, but there are two. Spring can't choose, so the app fails to start with an error like:

text
1NoUniqueBeanDefinitionException: expected single matching bean but found 2:
2emailNotificationService, smsNotificationService

Notice the names: by default, a bean's name is its class name with a lowercase first letter.

There are two ways to fix this.

@Primary: set a default

Mark one bean as the default choice:

@Primary
@Component
public class EmailNotificationService implements NotificationService { ... }

Now, wherever a NotificationService is needed, Spring picks email unless told otherwise.

@Qualifier: choose by name

Pick a specific bean at the place where it's injected:

@Service
public class OrderService {
private final NotificationService notificationService;
public OrderService(@Qualifier("smsNotificationService") NotificationService notificationService) {
this.notificationService = notificationService;
}
}

This OrderService gets SMS, even though email is @Primary. @Qualifier always wins over @Primary.

@Primary@Qualifier
Where it goesOn the beanAt the injection point
Meaning"Use me by default""Use this specific one here"
If both are usedLosesWins

Lombok note: if you use @RequiredArgsConstructor with @Qualifier on a field, Lombok won't copy the qualifier to the constructor by default. Add this to a lombok.config file in your project root:

text
1lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier

Implement DI in a Spring Boot project

Let's put it all together into a working project.

1. Create the project. Go to start.spring.io, choose Maven and Java, and generate the project. No extra dependencies are needed for this example.

2. Organize the packages. Spring only scans the main class's package and the packages below it:

text
1src/main/java/com/example/shop/
2├── ShopApplication.java
3├── notification/
4│ ├── NotificationService.java
5│ ├── EmailNotificationService.java
6│ └── SmsNotificationService.java
7└── order/
8 ├── OrderService.java
9 └── OrderRunner.java

3. The main class.

package com.example.shop;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ShopApplication {
public static void main(String[] args) {
SpringApplication.run(ShopApplication.class, args);
}
}

@SpringBootApplication turns on component scanning, so Spring finds every @Component and @Service under com.example.shop.

4. The interface and implementations.

package com.example.shop.notification;
public interface NotificationService {
void send(String to, String message);
}
package com.example.shop.notification;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Primary
@Component
public class EmailNotificationService implements NotificationService {
@Override
public void send(String to, String message) {
System.out.println("Email to " + to + ": " + message);
}
}
package com.example.shop.notification;
import org.springframework.stereotype.Component;
@Component
public class SmsNotificationService implements NotificationService {
@Override
public void send(String to, String message) {
System.out.println("SMS to " + to + ": " + message);
}
}

5. The service.

package com.example.shop.order;
import com.example.shop.notification.NotificationService;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final NotificationService notificationService;
public OrderService(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void placeOrder(String customer, String product) {
System.out.println("Order placed: " + product);
notificationService.send(customer, "Your order for " + product + " is confirmed");
}
}

6. Run something at startup. A CommandLineRunner bean runs once after the app starts. Spring injects OrderService into it:

package com.example.shop.order;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class OrderRunner implements CommandLineRunner {
private final OrderService orderService;
public OrderRunner(OrderService orderService) {
this.orderService = orderService;
}
@Override
public void run(String... args) {
orderService.placeOrder("asha@example.com", "Laptop");
}
}

7. Run it.

text
1mvn spring-boot:run

Output:

text
1Order placed: Laptop
2Email to asha@example.com: Your order for Laptop is confirmed

Spring found all the beans, saw that OrderRunner needs OrderService and OrderService needs NotificationService, picked email because of @Primary, created everything in the right order, and connected it all.

There isn't a single new for our classes anywhere in the project. To switch to SMS, change only OrderService's constructor:

public OrderService(@Qualifier("smsNotificationService") NotificationService notificationService) {
this.notificationService = notificationService;
}
text
1Order placed: Laptop
2SMS to asha@example.com: Your order for Laptop is confirmed