How do I use external configuration with Spring?

External configuration means keeping settings such as application names, URLs, ports, feature flags, credentials, or environment-specific values outside your Java code.

Spring supports this mainly through:

  • application.properties
  • application.yml
  • environment variables
  • command-line arguments
  • external property files
  • @Value
  • @ConfigurationProperties

1. Using application.properties

In a Spring or Spring Boot application, you can place configuration in:

src/main/resources/application.properties

Example:

app.name=My Spring App
app.version=1.0.0
app.description=Example application using external configuration

Then inject values with @Value:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AppProperties {

    @Value("${app.name}")
    private String appName;

    @Value("${app.version}")
    private String appVersion;

    @Value("${app.description}")
    private String appDescription;

    public void printProperties() {
        System.out.println("App Name: " + appName);
        System.out.println("App Version: " + appVersion);
        System.out.println("App Description: " + appDescription);
    }
}

2. Using application.yml

You can also use YAML:

app:
  name: My Spring App
  version: 1.0.0
  description: Example application using external configuration

The same @Value expressions still work:

@Value("${app.name}")
private String appName;

3. Using @PropertySource in non-Boot Spring

If you are using plain Spring with Java configuration, register a property file using @PropertySource:

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

@Configuration
@ComponentScan("com.example.app")
@PropertySource("classpath:application.properties")
public class AppConfig {
}

Then Spring can resolve values like:

@Value("${app.name}")
private String appName;

If you are using Spring Boot, you usually do not need @PropertySource for application.properties or application.yml. Spring Boot loads them automatically.


4. Recommended Spring Boot Approach: @ConfigurationProperties

For multiple related properties, prefer @ConfigurationProperties over many @Value fields.

Example application.properties:

app.name=My Spring App
app.version=1.0.0
app.description=Example application using external configuration

Create a properties class:

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {

    private String name;
    private String version;
    private String description;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

Then inject it into another bean:

import org.springframework.stereotype.Service;

@Service
public class GreetingService {

    private final AppProperties appProperties;

    public GreetingService(AppProperties appProperties) {
        this.appProperties = appProperties;
    }

    public void printGreeting() {
        System.out.println("Welcome to " + appProperties.getName());
        System.out.println("Version: " + appProperties.getVersion());
        System.out.println(appProperties.getDescription());
    }
}

5. External Files Outside the JAR

You can override configuration from outside the application.

For Spring Boot:

java -jar myapp.jar --spring.config.location=file:/opt/myapp/application.properties

Or include an additional config file:

java -jar myapp.jar --spring.config.additional-location=file:/opt/myapp/

Example external file:

app.name=Production App
app.version=2.0.0
app.description=Running with production configuration

6. Environment Variables

Spring Boot can read environment variables automatically.

For example, this property:

app.name=My Spring App

Can be overridden with:

APP_NAME=Production App

Spring Boot maps environment variable names to property names using relaxed binding:

APP_NAME -> app.name
SERVER_PORT -> server.port
SPRING_DATASOURCE_URL -> spring.datasource.url

7. Command-Line Arguments

You can override properties when starting the application:

java -jar myapp.jar --app.name="Command Line App" --server.port=9090

Command-line arguments usually have high priority and override values from property files.


8. Profiles for Environment-Specific Config

Profiles let you separate configuration by environment.

Common files:

application.properties
application-dev.properties
application-test.properties
application-prod.properties

Example:

# application-prod.properties
app.name=Production App
server.port=8080

Run with a profile:

java -jar myapp.jar --spring.profiles.active=prod

Or set an environment variable:

SPRING_PROFILES_ACTIVE=prod

9. Default Values with @Value

You can provide fallback values:

@Value("${app.name:Default App}")
private String appName;

If app.name is missing, Spring uses "Default App".


10. Common Property Examples

server.port=8081

spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=myuser
spring.datasource.password=secret

logging.level.org.springframework=INFO

app.name=My Spring App
app.version=1.0.0

Summary

Use external configuration like this:

Use case Recommended approach
Simple single value @Value("${property.name}")
Group of related settings @ConfigurationProperties
Spring Boot default config application.properties or application.yml
Plain Spring Java config @PropertySource
Environment-specific config Spring profiles
Production overrides external file, environment variables, or command-line args

For most Spring Boot applications, the usual setup is:

src/main/resources/application.properties

plus a configuration class using:

@ConfigurationProperties(prefix = "app")

This keeps configuration clean, type-safe, and easy to override per environment.

How do I manage bean scope in Spring?

In Spring, bean scope controls how many instances of a bean Spring creates and how long those instances live.

By default, Spring beans are singleton scoped, meaning Spring creates one shared instance per ApplicationContext.


Common Spring Bean Scopes

Scope Meaning
singleton One shared instance per Spring container
prototype A new instance every time the bean is requested
request One instance per HTTP request
session One instance per HTTP session
application One instance per ServletContext
websocket One instance per WebSocket session

The most commonly used scopes are:

  • singleton
  • prototype
  • request
  • session

1. Singleton Scope

singleton is the default scope.

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
@Scope("singleton")
public class UserService {
}

This is equivalent to:

import org.springframework.stereotype.Service;

@Service
public class UserService {
}

Spring creates only one UserService object, and every injection point receives the same instance.


Example

@Service
public class CounterService {

    private int count = 0;

    public int increment() {
        return ++count;
    }
}

Because this bean is singleton scoped, the count field is shared across the application.

That means singleton beans should usually be stateless, especially in web applications.

Prefer this:

@Service
public class PriceCalculator {

    public BigDecimal calculatePrice(BigDecimal amount) {
        return amount.multiply(new BigDecimal("1.10"));
    }
}

Avoid storing request-specific or user-specific data in singleton beans.


2. Prototype Scope

A prototype bean creates a new instance every time Spring is asked for the bean.

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

@Component
@Scope("prototype")
public class ReportBuilder {

    private String title;

    public void setTitle(String title) {
        this.title = title;
    }

    public String build() {
        return "Report: " + title;
    }
}

Each direct request to Spring for ReportBuilder creates a new object.


Prototype with @Bean

You can also define prototype scope on a @Bean method:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class AppConfig {

    @Bean
    @Scope("prototype")
    public ReportBuilder reportBuilder() {
        return new ReportBuilder();
    }
}

3. Important: Prototype Inside Singleton

A common surprise is this:

@Service
public class ReportService {

    private final ReportBuilder reportBuilder;

    public ReportService(ReportBuilder reportBuilder) {
        this.reportBuilder = reportBuilder;
    }
}

If ReportService is singleton and ReportBuilder is prototype, Spring injects one prototype instance when ReportService is created.

It does not automatically create a new ReportBuilder every time you use it.


Correct Way: Use ObjectProvider

If a singleton needs a fresh prototype instance on demand, use ObjectProvider.

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;

@Service
public class ReportService {

    private final ObjectProvider<ReportBuilder> reportBuilderProvider;

    public ReportService(ObjectProvider<ReportBuilder> reportBuilderProvider) {
        this.reportBuilderProvider = reportBuilderProvider;
    }

    public String createReport(String title) {
        ReportBuilder builder = reportBuilderProvider.getObject();
        builder.setTitle(title);
        return builder.build();
    }
}

Now each call to getObject() returns a new prototype instance.


4. Request Scope

request scope creates one bean instance per HTTP request.

This is useful in Spring MVC applications for request-specific data.

import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;

@Component
@RequestScope
public class RequestContext {

    private String correlationId;

    public String getCorrelationId() {
        return correlationId;
    }

    public void setCorrelationId(String correlationId) {
        this.correlationId = correlationId;
    }
}

Each HTTP request gets its own RequestContext.


5. Session Scope

session scope creates one bean instance per HTTP session.

import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.SessionScope;

@Component
@SessionScope
public class ShoppingCart {

    private final List<String> items = new ArrayList<>();

    public void addItem(String item) {
        items.add(item);
    }

    public List<String> getItems() {
        return items;
    }
}

Each user session gets its own ShoppingCart.

This is useful for things like:

  • shopping carts
  • user preferences
  • wizard-style form state

6. Using Scope Constants

Instead of writing scope names as strings, you can use Spring constants.

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ReportBuilder {
}

For singleton:

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class UserService {
}

7. XML Configuration Example

If you use XML configuration, define the scope with the scope attribute.

Singleton

<bean id="service" class="com.example.DummyService" scope="singleton"/>

Since singleton is the default, this is also valid:

<bean id="service" class="com.example.DummyService"/>

Prototype

<bean id="service" class="com.example.DummyService" scope="prototype"/>

With singleton scope, repeated calls to getBean("service") return the same object.

With prototype scope, repeated calls to getBean("service") return different objects.


8. Lifecycle Difference

Singleton beans are fully managed by Spring, including destruction callbacks.

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Service;

@Service
public class CacheService {

    @PostConstruct
    public void init() {
        System.out.println("Cache initialized");
    }

    @PreDestroy
    public void shutdown() {
        System.out.println("Cache shutting down");
    }
}

For singleton beans:

  • Spring creates the bean
  • Spring initializes it
  • Spring calls destruction callbacks when the context closes

For prototype beans:

  • Spring creates and initializes the bean
  • Spring does not manage the full destruction lifecycle after handing it out

So if a prototype bean holds resources, your application is responsible for cleanup.


9. Choosing the Right Scope

Use this as a practical guide:

Use case Recommended scope
Stateless service singleton
Repository/data access object singleton
Controller usually singleton
Stateful object created per use prototype
Request-specific data request
User-session data session

Best Practices

  1. Keep singleton beans stateless when possible.
  2. Do not store user-specific data in singleton services.
  3. Use prototype for objects that need independent state per use.
  4. Use ObjectProvider<T> when a singleton needs fresh prototype instances.
  5. Use @RequestScope for per-request web data.
  6. Use @SessionScope carefully, because session state consumes memory.
  7. Prefer annotation-based configuration in modern Spring applications.
  8. Use XML scope configuration only when working with XML-based Spring setup.

Bottom Line

You manage bean scope in Spring by declaring the scope on the bean:

@Component
@Scope("prototype")
public class MyBean {
}

or:

@Bean
@Scope("prototype")
public MyBean myBean() {
    return new MyBean();
}

If you do not specify a scope, Spring uses:

singleton

So in most applications, services, repositories, and controllers are singleton beans, while stateful per-use objects should be prototype, request, or session scoped depending on how long their state should live.

How do I inject dependencies using constructor injection?

Use constructor injection by declaring your dependency as a private final field and accepting it as a constructor parameter. Spring will create the dependency bean and pass it into the constructor automatically.

Example:

import org.springframework.stereotype.Component;

@Component
public class MyDependency {

    public void doSomething() {
        System.out.println("Dependency logic executed.");
    }
}
import org.springframework.stereotype.Service;

@Service
public class MyService {

    private final MyDependency myDependency;

    public MyService(MyDependency myDependency) {
        this.myDependency = myDependency;
    }

    public void doWork() {
        myDependency.doSomething();
    }
}

Spring can inject MyDependency because:

  1. MyDependency is a Spring bean, for example annotated with @Component.
  2. MyService is also a Spring bean, for example annotated with @Service.
  3. MyService has a constructor that requires MyDependency.

In modern Spring, if the class has only one constructor, you usually do not need @Autowired:

@Service
public class MyService {

    private final MyDependency myDependency;

    public MyService(MyDependency myDependency) {
        this.myDependency = myDependency;
    }
}

If you use Lombok, you can make it shorter:

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class MyService {

    private final MyDependency myDependency;

    public void doWork() {
        myDependency.doSomething();
    }
}

Constructor injection is recommended because it makes dependencies explicit, allows fields to be final, improves testability, and prevents partially initialized objects.

How do I use @Component, @Service, and @Repository correctly?

Short Answer

Use these annotations according to the role of the class:

Annotation Use for Typical layer
@Component Generic Spring-managed class Utility/infrastructure/helper
@Service Business logic Service layer
@Repository Data access / persistence Repository/DAO layer

All three make the class a Spring bean, meaning Spring can create it, manage it, and inject it into other beans.


1. @Component: Generic Spring Bean

Use @Component when the class should be managed by Spring but does not clearly belong to the service, repository, or controller layer.

import org.springframework.stereotype.Component;

@Component
public class FileNameGenerator {

    public String generate(String originalName) {
        return System.currentTimeMillis() + "-" + originalName;
    }
}

Good uses for @Component:

  • formatters
  • mappers
  • validators
  • helpers
  • schedulers
  • adapters
  • general infrastructure classes

If a class contains business logic, prefer @Service instead.


2. @Service: Business Logic

Use @Service for classes that represent application/business operations.

import org.springframework.stereotype.Service;

@Service
public class EmployeeService {

    private final EmployeeRepository employeeRepository;

    public EmployeeService(EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }

    public Employee getEmployee(Long id) {
        return employeeRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Employee not found"));
    }
}

Good uses for @Service:

  • coordinating business workflows
  • applying business rules
  • calling repositories
  • calling external APIs
  • handling transactions

Example:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class PayrollService {

    private final EmployeeRepository employeeRepository;

    public PayrollService(EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }

    @Transactional
    public void processPayroll(Long employeeId) {
        Employee employee = employeeRepository.findById(employeeId)
                .orElseThrow(() -> new IllegalArgumentException("Employee not found"));

        // business logic here
    }
}

@Service is technically a specialized @Component, but it communicates intent:
this class contains business/service logic.


3. @Repository: Database/Data Access

Use @Repository for persistence classes: DAOs, database gateways, or repositories.

With Spring Data JPA, you usually define an interface:

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

For Spring Data JPA interfaces, @Repository is often optional because Spring Data can detect repository interfaces automatically, but adding it is still common and makes the role explicit.

Use @Repository for:

  • JPA repositories
  • JDBC DAOs
  • persistence adapters
  • custom database access classes

Example custom DAO:

import jakarta.persistence.EntityManager;
import org.springframework.stereotype.Repository;

@Repository
public class EmployeeDao {

    private final EntityManager entityManager;

    public EmployeeDao(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public Employee findById(Long id) {
        return entityManager.find(Employee.class, id);
    }
}

@Repository also has an extra Spring meaning: it can participate in persistence exception translation, where database-specific exceptions are translated into Spring’s data access exception hierarchy.


4. Recommended Layering

A typical Spring MVC + Spring Data JPA flow looks like this:

Controller -> Service -> Repository -> Database

Example:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class EmployeeController {

    private final EmployeeService employeeService;

    public EmployeeController(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }

    @GetMapping("/employees/{id}")
    public Employee getEmployee(@PathVariable Long id) {
        return employeeService.getEmployee(id);
    }
}
import org.springframework.stereotype.Service;

@Service
public class EmployeeService {

    private final EmployeeRepository employeeRepository;

    public EmployeeService(EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }

    public Employee getEmployee(Long id) {
        return employeeRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Employee not found"));
    }
}
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

5. Prefer Constructor Injection

For all of these beans, prefer constructor injection:

import org.springframework.stereotype.Service;

@Service
public class OrderService {

    private final PaymentClient paymentClient;

    public OrderService(PaymentClient paymentClient) {
        this.paymentClient = paymentClient;
    }

    public void placeOrder() {
        paymentClient.charge();
    }
}

Avoid field injection like this:

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

@Service
public class OrderService {

    @Autowired
    private PaymentClient paymentClient;
}

Field injection works, but constructor injection is usually better because:

  • dependencies are explicit
  • fields can be final
  • the class is easier to test
  • the object cannot be created without required dependencies

If you use Lombok, this is common:

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class OrderService {

    private final PaymentClient paymentClient;

    public void placeOrder() {
        paymentClient.charge();
    }
}

6. Common Mistakes

Mistake 1: Using @Component for everything

This works:

@Component
public class EmployeeService {
}

But this is clearer:

@Service
public class EmployeeService {
}

Use the most specific annotation when possible.


Mistake 2: Putting business logic in repositories

Avoid this:

@Repository
public class EmployeeRepository {

    public void calculateBonusAndSaveEmployee() {
        // business rules mixed with database access
    }
}

Prefer:

Service: business rules
Repository: database access

Mistake 3: Injecting repositories directly into controllers

This is not always wrong, but for non-trivial applications it usually leads to poor layering.

Less ideal:

@RestController
public class EmployeeController {

    private final EmployeeRepository employeeRepository;

    public EmployeeController(EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }
}

Better:

@RestController
public class EmployeeController {

    private final EmployeeService employeeService;

    public EmployeeController(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }
}

The service layer gives you a place for validation, transactions, business rules, and orchestration.


7. Component Scanning Matters

Spring only finds these annotations if the classes are inside packages that Spring scans.

In Spring Boot, this usually works automatically if your main class is in the root package:

com.example.app
├── Application.java
├── controller
│   └── EmployeeController.java
├── service
│   └── EmployeeService.java
└── repository
    └── EmployeeRepository.java

If your annotated classes are outside the scanned package, Spring will not create beans for them.


Rule of Thumb

Use this:

@Component   = generic Spring-managed class
@Service     = business logic
@Repository = data access
@Controller / @RestController = web layer

For most applications:

@RestController
public class EmployeeController {
    private final EmployeeService employeeService;
}
@Service
public class EmployeeService {
    private final EmployeeRepository employeeRepository;
}
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

That is the standard and correct way to use them.

How do I understand the Spring ApplicationContext?

The Spring ApplicationContext is the central runtime container of a Spring application.

In simple terms:

ApplicationContext is the object that holds your Spring application together.

It knows:

  • which objects Spring should manage
  • how those objects are created
  • how dependencies are injected
  • which configuration values are available
  • which beans need lifecycle callbacks
  • which features like transactions, events, MVC, or JPA are enabled

1. The Short Definition

A Spring ApplicationContext is a container for Spring beans.

A bean is an object managed by Spring.

For example, if you have:

@Service
public class UserService {
}

Spring creates an instance of UserService and stores/manages it inside the ApplicationContext.

Conceptually:

ApplicationContext
 ├── userService
 ├── userRepository
 ├── orderService
 ├── dataSource
 ├── transactionManager
 └── many internal Spring infrastructure beans

2. Why Does Spring Need an ApplicationContext?

Without Spring, you create and connect objects yourself:

UserRepository repository = new UserRepository();
UserService service = new UserService(repository);

With Spring, you describe the objects and dependencies, and Spring does the wiring:

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}
@Repository
public class UserRepository {
}

Spring sees both classes, creates both objects, and injects UserRepository into UserService.

The place where Spring manages all of this is the ApplicationContext.


3. What the ApplicationContext Contains

The ApplicationContext contains two broad categories of things:

Your application beans

Examples:

UserController
UserService
UserRepository
OrderService
PaymentService

These are the objects you usually write.

Spring infrastructure beans

Examples:

DataSource
EntityManagerFactory
TransactionManager
MessageSource
ConversionService
HandlerMapping
BeanPostProcessor

These are objects Spring uses internally to provide features like:

  • dependency injection
  • transactions
  • validation
  • Spring MVC routing
  • database integration
  • event publishing
  • configuration loading

So the context contains both your application and the framework infrastructure around it.


4. What Happens When the ApplicationContext Starts?

When a Spring application starts, Spring creates an ApplicationContext.

A simplified startup flow looks like this:

1. Create the ApplicationContext
2. Read configuration classes, annotations, properties, and component scans
3. Discover bean definitions
4. Decide which beans should exist
5. Create singleton beans
6. Inject dependencies
7. Run bean lifecycle callbacks
8. Apply bean post-processors
9. Create proxies if needed
10. Publish startup events
11. Application is ready

A key detail: Spring first builds bean definitions, then creates actual bean instances.


5. Bean Definition vs. Bean Instance

This distinction helps a lot.

A bean definition is Spring’s recipe for creating a bean.

It includes information like:

Bean name: userService
Bean type: UserService
Scope: singleton
Dependencies: userRepository
Initialization method: maybe present
Lazy or eager: depends on configuration

A bean instance is the actual object created from that recipe.

So conceptually:

Bean definition:
  "I know how to create UserService."

Bean instance:
  new UserService(userRepository)

The ApplicationContext manages both the recipes and the actual objects.


6. Most Beans Are Singletons by Default

By default, Spring creates one shared instance of each bean per ApplicationContext.

That means this:

@Service
public class UserService {
}

usually results in one UserService object shared wherever it is injected.

So if three controllers need UserService, they all receive the same Spring-managed instance.

UserController  ─┐
AdminController ─┼──> same UserService bean
ReportController ┘

This is why Spring service beans should usually be stateless or carefully designed for thread safety.


7. You Usually Do Not Use ApplicationContext Directly

You can ask the context for a bean:

ApplicationContext context = ...;
UserService userService = context.getBean(UserService.class);

But in normal Spring application code, you usually should not do this.

Prefer dependency injection:

@Service
public class ReportService {

    private final UserService userService;

    public ReportService(UserService userService) {
        this.userService = userService;
    }
}

This is better because:

  • dependencies are explicit
  • the class is easier to test
  • the class is not tightly coupled to Spring’s container API
  • Spring can validate dependencies at startup

Use ApplicationContext#getBean() only when you truly need dynamic lookup.


8. ApplicationContext Is More Than a Bean Factory

Spring has a lower-level interface called BeanFactory.

A BeanFactory can create and manage beans.

ApplicationContext extends that idea and adds higher-level application features, such as:

  • event publishing
  • internationalization/message resolution
  • resource loading
  • environment and property access
  • integration with Spring AOP
  • web application support
  • lifecycle management

So you can think of it like this:

BeanFactory:
  Basic bean creation and dependency injection

ApplicationContext:
  BeanFactory + application-level services

In most real applications, you work with ApplicationContext, not directly with BeanFactory.


9. ApplicationContext and Dependency Injection

The most important job of the ApplicationContext is dependency injection.

Given this:

@Service
public class OrderService {

    private final PaymentService paymentService;
    private final OrderRepository orderRepository;

    public OrderService(
            PaymentService paymentService,
            OrderRepository orderRepository
    ) {
        this.paymentService = paymentService;
        this.orderRepository = orderRepository;
    }
}

Spring roughly does this:

1. See that OrderService is a bean
2. See that it needs PaymentService and OrderRepository
3. Find matching beans
4. Create those dependencies if needed
5. Call the OrderService constructor
6. Store the created OrderService bean in the context

You do not manually create the dependency graph. The ApplicationContext does.


10. ApplicationContext and Proxies

Sometimes the object you get from Spring is not the raw object you wrote.

It may be a proxy.

For example:

@Service
public class TransferService {

    @Transactional
    public void transferMoney() {
        // database work
    }
}

When @Transactional is involved, Spring may wrap TransferService in a proxy.

Conceptually:

Caller
  ↓
Transaction proxy
  ↓
Real TransferService

The proxy adds behavior around your method call:

begin transaction
call transferMoney()
commit transaction

or, if there is an error:

rollback transaction

This is why calls between Spring beans often need to go through the Spring-managed object, not through manually created objects.

The ApplicationContext is responsible for creating and managing those proxied beans.


11. ApplicationContext in a Web Application

In a Spring MVC web application, the ApplicationContext also contains web-related infrastructure.

For example:

Controller beans
Handler mappings
Request mappings
Message converters
Validation support
Exception handlers
View resolvers, if using server-side views

When a request arrives, Spring MVC uses beans from the context to decide:

GET /users/42

maps to something like:

@GetMapping("/users/{id}")
public UserDto findUser(@PathVariable Long id) {
    // ...
}

So in a web app, the context is not just managing services and repositories. It also supports request handling.


12. ApplicationContext in Spring Data JPA

With Spring Data JPA, repository interfaces are also managed through the context.

For example:

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

You do not create the implementation manually.

Spring Data creates a repository bean and registers it in the ApplicationContext.

Then you can inject it:

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

The context knows that UserRepository is a Spring-managed bean, even though you did not write the implementation class yourself.


13. Common Types of ApplicationContext

Different application types use different context implementations.

For annotation-based configuration, you may see:

AnnotationConfigApplicationContext

For web applications, Spring uses web-aware contexts.

In Spring Boot, you often do not create the context directly. You usually start the app with:

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

SpringApplication.run(...) creates and starts the ApplicationContext for you.


14. A Small Manual Example

In a non-Boot or learning example, you might create the context manually:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {

    public static void main(String[] args) {
        ApplicationContext context =
                new AnnotationConfigApplicationContext(AppConfig.class);

        GreetingService greetingService =
                context.getBean(GreetingService.class);

        System.out.println(greetingService.greet("World"));
    }
}

But in real application classes, prefer injection over calling getBean().


15. Mental Model

A useful mental model is:

ApplicationContext = Spring's runtime registry and factory

It knows:
  - what beans exist
  - how to create them
  - how to connect them
  - how to configure them
  - when to initialize them
  - when to destroy them
  - whether to wrap them in proxies

Your code says:

I need a UserRepository.
I need a PaymentService.
This class is a controller.
This method should be transactional.
This property comes from configuration.

The ApplicationContext says:

I will create those objects,
wire them together,
apply the configuration,
wrap them if needed,
and make the application ready to run.

16. Practical Rules

When working with the ApplicationContext, remember these rules:

  1. Most application objects should be Spring beans.
  2. Use constructor injection for dependencies.
  3. Avoid manually calling new for services, repositories, and controllers.
  4. Avoid frequent direct use of ApplicationContext#getBean().
  5. Keep singleton beans stateless when possible.
  6. Remember that Spring may inject a proxy, not the raw class.
  7. If a bean is missing, check scanning, configuration, profiles, and conditional annotations.
  8. If multiple beans match, use @Primary or @Qualifier.

Bottom Line

The Spring ApplicationContext is the running Spring container.

It is responsible for:

  • discovering beans
  • creating beans
  • injecting dependencies
  • managing lifecycle
  • loading configuration
  • publishing events
  • supporting framework features
  • creating proxies for behavior like transactions

The shortest explanation is:

ApplicationContext is Spring’s runtime container: it creates, stores, wires, configures, and manages the objects that make up your application.

How do I use component scanning in Spring?

Component scanning is how Spring automatically finds your classes and registers them as beans.

Instead of manually creating every bean, you annotate classes with Spring stereotypes like:

@Component
@Service
@Repository
@Controller
@RestController

Then Spring scans selected packages, finds those classes, creates bean instances, and makes them available for dependency injection.


1. Basic Example

import org.springframework.stereotype.Service;

@Service
public class UserService {

    public String getUserName() {
        return "Alice";
    }
}

Because UserService is annotated with @Service, Spring can discover it during component scanning.

You can inject it into another bean:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/user")
    public String getUser() {
        return userService.getUserName();
    }
}

Spring automatically:

  1. finds UserService
  2. creates a UserService bean
  3. finds UserController
  4. creates a UserController bean
  5. injects UserService into UserController

2. Component Scanning in Spring Boot

In Spring Boot, component scanning is usually enabled automatically by @SpringBootApplication.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

@SpringBootApplication includes @ComponentScan.

By default, Spring Boot scans:

  • the package containing the main application class
  • all subpackages under it

For example:

com.example.demo
├── MyApplication.java
├── controller
│   └── UserController.java
├── service
│   └── UserService.java
└── repository
    └── UserRepository.java

If MyApplication is in com.example.demo, Spring scans:

com.example.demo
com.example.demo.controller
com.example.demo.service
com.example.demo.repository

This is the recommended structure.


3. Important Package Rule

Your main application class should usually be in the root package.

Good:

com.example.app
├── Application.java
├── user
│   ├── UserController.java
│   └── UserService.java
└── order
    ├── OrderController.java
    └── OrderService.java

Less ideal:

com.example.app.web
└── Application.java

com.example.app.service
└── UserService.java

If Application is inside com.example.app.web, Spring Boot scans com.example.app.web and its subpackages, but not sibling packages like com.example.app.service.


4. Manually Configure Component Scanning

If needed, you can specify packages explicitly with @ComponentScan.

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

@Configuration
@ComponentScan(basePackages = "com.example.app")
public class AppConfig {
}

Or with multiple packages:

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

@Configuration
@ComponentScan(basePackages = {
        "com.example.app.service",
        "com.example.app.repository",
        "com.example.shared"
})
public class AppConfig {
}

In Spring Boot, you can also place it on your main class:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = {
        "com.example.app",
        "com.example.shared"
})
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

5. Prefer basePackageClasses for Type Safety

Instead of using package names as strings, you can use classes as package markers.

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

@Configuration
@ComponentScan(basePackageClasses = {
        UserService.class,
        SharedComponent.class
})
public class AppConfig {
}

Spring scans the packages where those classes are located.

This is safer than string package names because refactoring tools can update class references.


6. Common Component Annotations

@Component

Generic Spring-managed bean.

import org.springframework.stereotype.Component;

@Component
public class FileStorage {
}

Use this when no more specific annotation fits.


@Service

Business logic or service layer.

import org.springframework.stereotype.Service;

@Service
public class PaymentService {
}

@Repository

Persistence or data access layer.

import org.springframework.stereotype.Repository;

@Repository
public class JdbcUserRepository {
}

For Spring Data JPA, repository interfaces are often detected separately by repository scanning:

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
}

You usually do not need to add @Repository to Spring Data JPA interfaces.


@Controller

Spring MVC controller that usually returns views.

import org.springframework.stereotype.Controller;

@Controller
public class PageController {
}

@RestController

REST API controller.

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HealthController {

    @GetMapping("/health")
    public String health() {
        return "OK";
    }
}

@RestController is equivalent to @Controller plus @ResponseBody.


7. Injecting Scanned Components

Once a class is discovered by component scanning, inject it using constructor injection.

import org.springframework.stereotype.Service;

@Service
public class OrderService {

    private final PaymentService paymentService;

    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void placeOrder() {
        paymentService.charge();
    }
}

With Lombok:

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class OrderService {

    private final PaymentService paymentService;

    public void placeOrder() {
        paymentService.charge();
    }
}

8. Excluding Classes from Component Scanning

You can exclude specific classes or patterns.

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

@Configuration
@ComponentScan(
        basePackages = "com.example.app",
        excludeFilters = @ComponentScan.Filter(
                type = FilterType.ASSIGNABLE_TYPE,
                classes = ExperimentalService.class
        )
)
public class AppConfig {
}

You can also exclude by annotation:

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

@Configuration
@ComponentScan(
        basePackages = "com.example.app",
        excludeFilters = @ComponentScan.Filter(
                type = FilterType.ANNOTATION,
                classes = DeprecatedComponent.class
        )
)
public class AppConfig {
}

9. Common Problems

Problem: Bean Not Found

Example error:

No qualifying bean of type 'com.example.UserService' available

Common causes:

  • the class is not annotated with @Component, @Service, etc.
  • the class is outside the scanned package
  • the class is abstract
  • the class has a failing constructor dependency
  • a required profile is not active
  • the bean is excluded by a scan filter

Problem: Controller Endpoint Not Working

Check that:

  • the controller has @Controller or @RestController
  • it is inside a scanned package
  • request mappings are correct
  • Spring MVC is enabled/configured
  • the application started without bean creation errors

Problem: Multiple Beans Found

If multiple scanned classes implement the same interface:

public interface PaymentProcessor {
    void process();
}
import org.springframework.stereotype.Service;

@Service
public class StripePaymentProcessor implements PaymentProcessor {

    @Override
    public void process() {
        // process with Stripe
    }
}
import org.springframework.stereotype.Service;

@Service
public class PaypalPaymentProcessor implements PaymentProcessor {

    @Override
    public void process() {
        // process with PayPal
    }
}

Injection by interface becomes ambiguous:

import org.springframework.stereotype.Service;

@Service
public class CheckoutService {

    public CheckoutService(PaymentProcessor paymentProcessor) {
    }
}

Fix it with @Primary:

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

@Service
@Primary
public class StripePaymentProcessor implements PaymentProcessor {

    @Override
    public void process() {
        // process with Stripe
    }
}

Or with @Qualifier:

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

@Service
public class CheckoutService {

    private final PaymentProcessor paymentProcessor;

    public CheckoutService(
            @Qualifier("paypalPaymentProcessor") PaymentProcessor paymentProcessor
    ) {
        this.paymentProcessor = paymentProcessor;
    }
}

10. Quick Rule of Thumb

For most Spring Boot applications:

  1. Put your main application class in the root package.
  2. Put controllers, services, repositories, and components in subpackages.
  3. Annotate classes with the right stereotype annotation.
  4. Use constructor injection.
  5. Avoid custom @ComponentScan unless you really need it.

A typical structure:

com.example.app
├── Application.java
├── controller
│   └── UserController.java
├── service
│   └── UserService.java
├── repository
│   └── UserRepository.java
└── config
    └── AppConfig.java

With this structure, Spring Boot component scanning usually works with no extra configuration.

How do I configure Spring using Java configuration?

Spring Java configuration lets you configure your application using Java classes instead of XML.

The main annotations are:

  • @Configuration — marks a class as a Spring configuration class
  • @Bean — declares a Spring bean manually
  • @ComponentScan — tells Spring where to find annotated components
  • @PropertySource — loads external properties
  • @Enable... annotations — enable specific Spring features, such as MVC, transactions, JPA, etc.

1. Create a configuration class

import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
}

@Configuration tells Spring that this class contains bean definitions and application setup.


2. Define beans manually with @Bean

Use @Bean when you want Spring to manage an object that you create yourself.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyService();
    }
}

Spring will create and manage the MyService instance.

By default, the bean name is the method name: myService.


3. Use component scanning

Instead of defining every bean manually, you can let Spring discover classes annotated with:

  • @Component
  • @Service
  • @Repository
  • @Controller
  • @RestController

Example:

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

@Configuration
@ComponentScan("com.example.app")
public class AppConfig {
}

Then Spring can find beans like this:

import org.springframework.stereotype.Service;

@Service
public class MyService {

    public void doWork() {
        System.out.println("Working...");
    }
}

4. Inject dependencies through constructors

Java configuration works together with dependency injection.

import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {

    public String findNameById(Long id) {
        return "Alice";
    }
}
import org.springframework.stereotype.Service;

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public String getUserName(Long id) {
        return userRepository.findNameById(id);
    }
}

If both classes are discovered by component scanning, Spring automatically injects UserRepository into UserService.


5. Bootstrapping Spring manually

For a non-Spring Boot application, you can start the Spring container like this:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {

    public static void main(String[] args) {
        ApplicationContext context =
                new AnnotationConfigApplicationContext(AppConfig.class);

        MyService myService = context.getBean(MyService.class);
        myService.doWork();
    }
}

6. Configure Spring MVC with Java configuration

For Spring MVC, use @EnableWebMvc and implement WebMvcConfigurer.

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
@ComponentScan("com.example.app")
public class WebConfig implements WebMvcConfigurer {
}

Example controller:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello from Spring MVC";
    }
}

7. Load properties

You can load a properties file with @PropertySource.

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig {
}

Then inject values with @Value:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AppInfo {

    private final String appName;

    public AppInfo(@Value("${app.name}") String appName) {
        this.appName = appName;
    }
}

Example application.properties:

app.name=My Spring App

8. Enable transactions

If you use database transactions, enable them with @EnableTransactionManagement.

import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
public class PersistenceConfig {
}

Then use @Transactional on services:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    @Transactional
    public void placeOrder() {
        // database operations
    }
}

Typical setup

A common Java configuration setup looks like this:

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan("com.example.app")
@PropertySource("classpath:application.properties")
@EnableTransactionManagement
public class AppConfig {
}

For Spring MVC:

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
@ComponentScan("com.example.app")
public class WebConfig implements WebMvcConfigurer {
}

Summary

To configure Spring using Java configuration:

  1. Create a class annotated with @Configuration.
  2. Add @ComponentScan to discover annotated classes.
  3. Use @Bean methods for manually created beans.
  4. Use constructor injection for dependencies.
  5. Add feature-specific annotations such as @EnableWebMvc, @EnableTransactionManagement, or JPA-related configuration as needed.

In most modern Spring applications, Java configuration plus component scanning replaces XML configuration almost entirely.

How do I create and manage beans in Spring?

In Spring, a bean is an object managed by the Spring container.

Spring is responsible for:

  • creating the object
  • injecting its dependencies
  • managing its lifecycle
  • applying configuration
  • destroying it when the application shuts down

The container that manages beans is usually the ApplicationContext.


1. What Is a Spring Bean?

A Spring bean is just a normal Java object whose lifecycle is controlled by Spring.

For example:

@Service
public class UserService {

    public String getMessage() {
        return "Hello from UserService";
    }
}

UserService is an ordinary Java class, but because it is annotated with @Service, Spring detects it and manages it as a bean.


2. Common Ways to Create Beans

There are two main ways to create beans in Spring:

  1. Component scanning
  2. Manual bean registration using @Bean

Option 1: Create Beans with Component Scanning

This is the most common approach.

Spring scans your project for classes annotated with stereotypes such as:

@Component
@Service
@Repository
@Controller
@RestController

Example:

@Service
public class EmailService {

    public void sendEmail(String to, String message) {
        System.out.println("Sending email to " + to + ": " + message);
    }
}

Spring automatically creates an EmailService bean.


Common Bean Annotations

@Component

Generic Spring-managed component.

@Component
public class FileStorage {
}

Use this when the class does not fit a more specific role.


@Service

Used for service/business logic classes.

@Service
public class PaymentService {
}

@Repository

Used for data access classes.

@Repository
public class UserRepository {
}

In Spring Data JPA, repositories are often interfaces:

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

Spring Data JPA creates the implementation automatically.


@Controller

Used for Spring MVC controllers that return views.

@Controller
public class PageController {
}

@RestController

Used for REST APIs.

@RestController
@RequestMapping("/api/users")
public class UserController {
}

@RestController is effectively @Controller plus @ResponseBody.


Option 2: Create Beans Manually with @Bean

Use @Bean when you want to create an object yourself and give it to Spring.

This is common for:

  • third-party classes
  • library objects
  • objects requiring special construction logic
  • configuration-based objects

Example:

@Configuration
public class AppConfig {

    @Bean
    public Clock clock() {
        return Clock.systemUTC();
    }
}

Now Spring manages a Clock bean.

You can inject it elsewhere:

@Service
public class TimeService {

    private final Clock clock;

    public TimeService(Clock clock) {
        this.clock = clock;
    }

    public Instant now() {
        return Instant.now(clock);
    }
}

@Component vs @Bean

Use @Component, @Service, or @Repository when the class is yours and should always be managed by Spring.

Use @Bean when you need explicit construction logic.

Example:

@Configuration
public class HttpClientConfig {

    @Bean
    public HttpClient httpClient() {
        return HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(10))
                .build();
    }
}

Here, HttpClient comes from the JDK, so you cannot annotate it with @Component.


3. Injecting Beans

Once Spring manages a bean, you usually use it through dependency injection.

The recommended style is constructor injection.

@Service
public class OrderService {

    private final PaymentService paymentService;
    private final EmailService emailService;

    public OrderService(PaymentService paymentService, EmailService emailService) {
        this.paymentService = paymentService;
        this.emailService = emailService;
    }

    public void placeOrder() {
        paymentService.charge();
        emailService.sendConfirmation();
    }
}

Spring sees that OrderService needs PaymentService and EmailService, then injects them automatically.


Constructor Injection with Lombok

If your project uses Lombok, you can write:

@Service
@RequiredArgsConstructor
public class OrderService {

    private final PaymentService paymentService;
    private final EmailService emailService;

    public void placeOrder() {
        paymentService.charge();
        emailService.sendConfirmation();
    }
}

@RequiredArgsConstructor generates the constructor for all final fields.

This is common in modern Spring applications.


4. Avoid Field Injection

You may see this style:

@Service
public class OrderService {

    @Autowired
    private PaymentService paymentService;
}

This works, but it is usually discouraged because:

  • it makes testing harder
  • dependencies are hidden
  • fields cannot be final
  • objects can be created in an invalid state

Prefer constructor injection instead.


5. Bean Names

Every bean has a name.

By default, Spring uses the class name with a lowercase-first letter.

@Service
public class PaymentService {
}

Default bean name:

paymentService

You can also give a custom name:

@Service("stripePaymentService")
public class StripePaymentService {
}

Or with @Bean:

@Bean("utcClock")
public Clock clock() {
    return Clock.systemUTC();
}

6. Handling Multiple Beans of the Same Type

If Spring finds multiple beans of the same type, the injection becomes ambiguous.

Example:

public interface PaymentProcessor {
    void process();
}
@Service
public class StripePaymentProcessor implements PaymentProcessor {

    @Override
    public void process() {
        System.out.println("Processing with Stripe");
    }
}
@Service
public class PaypalPaymentProcessor implements PaymentProcessor {

    @Override
    public void process() {
        System.out.println("Processing with PayPal");
    }
}

This is ambiguous:

@Service
public class CheckoutService {

    public CheckoutService(PaymentProcessor paymentProcessor) {
    }
}

Spring does not know which PaymentProcessor to inject.


Use @Primary

Mark one implementation as the default:

@Service
@Primary
public class StripePaymentProcessor implements PaymentProcessor {

    @Override
    public void process() {
        System.out.println("Processing with Stripe");
    }
}

Now Spring injects StripePaymentProcessor unless told otherwise.


Use @Qualifier

Choose a specific bean:

@Service
public class CheckoutService {

    private final PaymentProcessor paymentProcessor;

    public CheckoutService(
            @Qualifier("paypalPaymentProcessor") PaymentProcessor paymentProcessor
    ) {
        this.paymentProcessor = paymentProcessor;
    }
}

The qualifier usually matches the bean name.


7. Bean Scopes

By default, Spring beans are singleton scoped.

That means Spring creates one shared instance per application context.

@Service
public class UserService {
}

This is equivalent to:

@Scope("singleton")
@Service
public class UserService {
}

Common Bean Scopes

singleton

One instance per Spring container.

@Component
@Scope("singleton")
public class AppCache {
}

This is the default.


prototype

A new instance each time the bean is requested.

@Component
@Scope("prototype")
public class ReportBuilder {
}

request

One instance per HTTP request.

@Component
@RequestScope
public class RequestContext {
}

Useful in Spring MVC applications.


session

One instance per HTTP session.

@Component
@SessionScope
public class ShoppingCart {
}

8. Bean Lifecycle

Spring beans go through a lifecycle:

1. Bean definition discovered
2. Object created
3. Dependencies injected
4. Initialization callbacks run
5. Bean is ready to use
6. Destruction callbacks run when context closes

Initialization with @PostConstruct

With Jakarta imports, use:

import jakarta.annotation.PostConstruct;

@Service
public class CacheService {

    @PostConstruct
    public void init() {
        System.out.println("CacheService initialized");
    }
}

Cleanup with @PreDestroy

import jakarta.annotation.PreDestroy;

@Service
public class CacheService {

    @PreDestroy
    public void shutdown() {
        System.out.println("CacheService shutting down");
    }
}

9. Conditional Beans

Sometimes you only want a bean to exist under certain conditions.

In Spring Boot, common annotations include:

@ConditionalOnProperty
@ConditionalOnMissingBean
@ConditionalOnClass
@Profile

Example with profiles:

@Service
@Profile("dev")
public class DevEmailService implements EmailService {
}
@Service
@Profile("prod")
public class SmtpEmailService implements EmailService {
}

Run with:

spring.profiles.active=prod

Then only the prod bean is active.


10. Configuration Properties as Beans

For application configuration, prefer configuration properties instead of manually reading values.

@ConfigurationProperties(prefix = "mail")
public class MailProperties {

    private String host;
    private int port;

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }
}

Enable it:

@Configuration
@EnableConfigurationProperties(MailProperties.class)
public class MailConfig {
}

Example config:

mail.host=smtp.example.com
mail.port=587

Then inject it:

@Service
public class MailService {

    private final MailProperties mailProperties;

    public MailService(MailProperties mailProperties) {
        this.mailProperties = mailProperties;
    }
}

11. Getting Beans Programmatically

Most of the time, you should not call ApplicationContext#getBean() manually.

Prefer this:

@Service
public class ReportService {

    private final CsvExporter csvExporter;

    public ReportService(CsvExporter csvExporter) {
        this.csvExporter = csvExporter;
    }
}

Instead of this:

@Service
public class ReportService {

    private final ApplicationContext applicationContext;

    public ReportService(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public void export() {
        CsvExporter exporter = applicationContext.getBean(CsvExporter.class);
    }
}

Programmatic lookup is sometimes useful for dynamic behavior, but it should not be your default approach.


12. Dynamic or Lazy Bean Access

If you need lazy or optional access, prefer ObjectProvider.

@Service
public class NotificationService {

    private final ObjectProvider<SmsSender> smsSenderProvider;

    public NotificationService(ObjectProvider<SmsSender> smsSenderProvider) {
        this.smsSenderProvider = smsSenderProvider;
    }

    public void notifyUser(String phoneNumber, String message) {
        SmsSender smsSender = smsSenderProvider.getIfAvailable();

        if (smsSender != null) {
            smsSender.send(phoneNumber, message);
        }
    }
}

This avoids directly depending on ApplicationContext.


13. Lazy Beans

By default, singleton beans are usually created during application startup.

You can make a bean lazy:

@Service
@Lazy
public class ExpensiveService {
}

Or inject it lazily:

@Service
public class DashboardService {

    private final ExpensiveService expensiveService;

    public DashboardService(@Lazy ExpensiveService expensiveService) {
        this.expensiveService = expensiveService;
    }
}

14. Managing Beans in Tests

In Spring tests, beans can be injected into test classes:

@SpringBootTest
class OrderServiceTest {

    @Autowired
    private OrderService orderService;

    @Test
    void placesOrder() {
        orderService.placeOrder();
    }
}

You can replace beans with mocks using Spring Boot testing support:

@SpringBootTest
class OrderServiceTest {

    @MockBean
    private PaymentService paymentService;

    @Autowired
    private OrderService orderService;

    @Test
    void placesOrder() {
        orderService.placeOrder();
    }
}

For plain unit tests, you often do not need Spring:

class OrderServiceTest {

    @Test
    void placesOrder() {
        PaymentService paymentService = mock(PaymentService.class);
        EmailService emailService = mock(EmailService.class);

        OrderService orderService = new OrderService(paymentService, emailService);

        orderService.placeOrder();
    }
}

15. Practical Rules

Use these rules most of the time:

  1. Use @Service for business logic.
  2. Use @Repository for persistence/data access.
  3. Use @Controller or @RestController for web endpoints.
  4. Use @Component for general Spring-managed classes.
  5. Use @Bean for third-party objects or special construction logic.
  6. Prefer constructor injection.
  7. Avoid field injection.
  8. Avoid calling ApplicationContext#getBean() unless you truly need dynamic lookup.
  9. Use @Qualifier or @Primary when multiple beans share the same type.
  10. Keep singleton beans stateless when possible.

Minimal Example

@Service
public class GreetingService {

    public String greet(String name) {
        return "Hello, " + name;
    }
}
@RestController
@RequestMapping("/greetings")
public class GreetingController {

    private final GreetingService greetingService;

    public GreetingController(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    @GetMapping("/{name}")
    public String greet(@PathVariable String name) {
        return greetingService.greet(name);
    }
}

Spring will:

1. Find GreetingService
2. Create a GreetingService bean
3. Find GreetingController
4. Create a GreetingController bean
5. Inject GreetingService into GreetingController
6. Map GET /greetings/{name}
7. Call the controller method when a request arrives

Bottom Line

To create and manage beans in Spring:

  • annotate your classes with @Component, @Service, @Repository, or @Controller
  • define special beans with @Bean inside @Configuration
  • inject dependencies through constructors
  • let Spring manage lifecycle, scopes, configuration, and wiring

In most cases, you should declare what your application needs and let Spring create and connect the objects for you.

How do I use dependency injection in Spring?

In Spring, Dependency Injection (DI) means you let the Spring container create and provide the objects your class depends on, instead of manually creating them with new.

The recommended approach in modern Spring is constructor injection.

1. Define a dependency as a Spring bean

import org.springframework.stereotype.Component;

@Component
public class EmailSender {

    public void send(String message) {
        System.out.println("Sending email: " + message);
    }
}

@Component tells Spring: “Create and manage an instance of this class.”

2. Inject it into another bean

import org.springframework.stereotype.Service;

@Service
public class NotificationService {

    private final EmailSender emailSender;

    public NotificationService(EmailSender emailSender) {
        this.emailSender = emailSender;
    }

    public void notifyUser(String message) {
        emailSender.send(message);
    }
}

Because NotificationService is also a Spring bean, Spring sees its constructor and automatically provides an EmailSender.

In modern Spring, if there is only one constructor, you usually do not need @Autowired on the constructor.

3. Use the service from a controller or another bean

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class NotificationController {

    private final NotificationService notificationService;

    public NotificationController(NotificationService notificationService) {
        this.notificationService = notificationService;
    }

    @GetMapping("/notify")
    public String notifyUser() {
        notificationService.notifyUser("Hello from Spring!");
        return "Notification sent";
    }
}

Common DI styles in Spring

Constructor injection — recommended

@Service
public class MyService {

    private final MyDependency myDependency;

    public MyService(MyDependency myDependency) {
        this.myDependency = myDependency;
    }
}

Use this most of the time because it:

  • makes dependencies explicit
  • supports immutability with final
  • is easier to test
  • avoids partially initialized objects

Setter injection

@Service
public class MyService {

    private MyDependency myDependency;

    @Autowired
    public void setMyDependency(MyDependency myDependency) {
        this.myDependency = myDependency;
    }
}

Use setter injection mainly for optional dependencies or dependencies that may change after construction.

Field injection — usually avoid

@Service
public class MyService {

    @Autowired
    private MyDependency myDependency;
}

This works, but it is generally discouraged because it makes testing harder and hides the class’s required dependencies.

Injecting interfaces

A common pattern is to inject an interface rather than a concrete class:

public interface MessageSender {
    void send(String message);
}
import org.springframework.stereotype.Component;

@Component
public class EmailSender implements MessageSender {

    @Override
    public void send(String message) {
        System.out.println("Email: " + message);
    }
}
import org.springframework.stereotype.Service;

@Service
public class NotificationService {

    private final MessageSender messageSender;

    public NotificationService(MessageSender messageSender) {
        this.messageSender = messageSender;
    }

    public void notifyUser(String message) {
        messageSender.send(message);
    }
}

If there is only one implementation of MessageSender, Spring injects it automatically.

When there are multiple implementations

If multiple beans match the same type, use @Qualifier:

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

@Service
public class NotificationService {

    private final MessageSender messageSender;

    public NotificationService(@Qualifier("emailSender") MessageSender messageSender) {
        this.messageSender = messageSender;
    }
}

The qualifier value usually matches the bean name, which by default is the class name with a lowercase-first letter.

Creating beans with @Bean

You can also define beans in a configuration class:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    public EmailSender emailSender() {
        return new EmailSender();
    }
}

This is useful when:

  • the class comes from a third-party library
  • construction requires custom setup
  • you do not want to annotate the class with @Component

Summary

Use this pattern most of the time:

@Service
public class MyService {

    private final MyDependency myDependency;

    public MyService(MyDependency myDependency) {
        this.myDependency = myDependency;
    }

    public void doWork() {
        myDependency.doSomething();
    }
}

And make sure the dependency is a Spring bean:

@Component
public class MyDependency {

    public void doSomething() {
        System.out.println("Dependency logic executed.");
    }
}

In short: annotate your classes with Spring stereotypes like @Component, @Service, or @Repository, then inject dependencies through constructors.

How do I understand what the Spring Framework actually does?

The easiest way to understand what the Spring Framework actually does is to stop thinking of it as “magic” and start thinking of it as infrastructure code that your application delegates to.

At a high level:

Spring creates objects, wires them together, manages their lifecycle, and adds common behavior around them so you do not have to write that plumbing yourself.


1. The Core Problem Spring Solves

Without Spring, your application code often has to manually do things like:

UserRepository repository = new JdbcUserRepository(dataSource);
EmailService emailService = new SmtpEmailService(config);
UserService userService = new UserService(repository, emailService);

As the application grows, this becomes harder to manage:

  • Who creates each object?
  • In what order?
  • Which implementation should be used?
  • How are shared dependencies reused?
  • How do you add transactions, security, logging, validation, configuration, etc.?

Spring’s answer is:

Declare your application components, and I will create, connect, configure, and enhance them.


2. Spring Is Mainly an Application Container

The heart of Spring is the IoC container.

IoC means Inversion of Control.

Instead of your code controlling object creation:

UserService service = new UserService(new UserRepository());

Spring controls it:

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

Then Spring sees:

@Repository
public class UserRepository {
}

And automatically creates:

  • a UserRepository
  • a UserService
  • injects the repository into the service

This is called Dependency Injection.

So Spring’s first major job is:

Managing your application objects, called beans.


3. What Is a Bean?

A Spring bean is simply an object managed by Spring.

For example:

@Service
public class PaymentService {
}

That class becomes a Spring-managed object.

Spring can:

  • create it
  • inject its dependencies
  • configure it
  • call lifecycle methods
  • wrap it with proxies
  • destroy it when the application shuts down

The object itself is ordinary Java. What changes is who manages it.


4. Spring Reads Metadata About Your App

Spring needs to know what objects to manage.

You give it metadata using annotations such as:

@Component
@Service
@Repository
@Controller
@Configuration
@Bean
@Autowired

Example:

@Configuration
public class AppConfig {

    @Bean
    public Clock clock() {
        return Clock.systemUTC();
    }
}

This tells Spring:

“When something needs a Clock, use this object.”

Spring scans your code, reads these annotations, builds a registry of beans, and creates an application context.


5. The ApplicationContext Is the Running Spring Container

The ApplicationContext is basically Spring’s runtime environment.

It contains all managed beans.

Conceptually:

ApplicationContext context = ...;

UserService userService = context.getBean(UserService.class);

In most Spring applications, you do not usually call getBean() yourself. Spring injects dependencies automatically.

The container knows:

  • which beans exist
  • how to create them
  • what dependencies they need
  • what order to initialize them in
  • what configuration values they require

6. Spring Adds Behavior Using Proxies

A lot of Spring’s “magic” comes from proxies.

For example, when you write:

@Transactional
public void transferMoney(Account from, Account to, BigDecimal amount) {
    withdraw(from, amount);
    deposit(to, amount);
}

Spring does not rewrite your method.

Instead, it may create a wrapper object around your service.

Conceptually:

beginTransaction();

try {
    transferMoney(...);
    commitTransaction();
} catch (Exception ex) {
    rollbackTransaction();
    throw ex;
}

That wrapper is a proxy.

Spring uses proxies for features like:

  • transactions
  • security
  • caching
  • async methods
  • method validation
  • aspect-oriented programming

So another major thing Spring does is:

It intercepts calls to your objects and adds infrastructure behavior around them.


7. Spring MVC Handles Web Requests

If you use Spring MVC, Spring also acts as a web framework.

You write:

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public UserDto getUser(@PathVariable Long id) {
        return new UserDto(id, "Alice");
    }
}

Spring MVC handles:

  • receiving the HTTP request
  • matching /users/{id} to the method
  • converting path variables
  • calling your controller
  • converting the return value to JSON
  • writing the HTTP response

You focus on:

public UserDto getUser(Long id)

Spring handles the web plumbing.


8. Spring Data JPA Creates Repository Implementations

With Spring Data JPA, you can write:

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

You do not manually implement this interface.

Spring Data creates an implementation at runtime.

It understands method names like:

findByEmail
findByStatus
findByCreatedAtAfter

And turns them into database queries.

So Spring Data JPA does:

  • repository implementation generation
  • query method parsing
  • transaction integration
  • JPA EntityManager management

9. Spring Boot vs Spring Framework

This is an important distinction.

Spring Framework

The Spring Framework provides the core capabilities:

  • dependency injection
  • bean lifecycle
  • transactions
  • Spring MVC
  • validation integration
  • resource loading
  • AOP
  • event system

Spring Boot

Spring Boot sits on top of Spring Framework.

It adds:

  • auto-configuration
  • embedded servers
  • starter dependencies
  • production features
  • simplified project setup

Spring Boot’s job is mostly:

“Based on what dependencies and settings you have, I will configure Spring automatically.”

For example, if Spring Boot sees Spring MVC on the classpath, it configures a web server and MVC infrastructure.

If it sees Spring Data JPA and a database driver, it configures JPA-related beans.


10. A Mental Model

Think of a Spring application like this:

Your code:
  Controllers
  Services
  Repositories
  Entities
  Configuration

Spring:
  Creates objects
  Injects dependencies
  Applies configuration
  Opens transactions
  Handles HTTP requests
  Converts JSON
  Manages validation
  Integrates with databases
  Publishes events
  Handles lifecycle

You write the business logic.

Spring handles the surrounding infrastructure.


11. What Happens at Startup?

Simplified startup flow:

1. Application starts
2. Spring creates an ApplicationContext
3. Spring scans classes and configuration
4. Spring discovers bean definitions
5. Spring creates beans
6. Spring injects dependencies
7. Spring applies post-processors
8. Spring creates proxies where needed
9. Web server starts, if this is a web app
10. App is ready to receive requests

For example:

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

That one line starts a large amount of framework infrastructure.


12. A Practical Way to Learn Spring

To understand Spring deeply, learn it in this order:

  1. Plain Java object creation
  2. Dependency Injection
  3. Beans and ApplicationContext
  4. Configuration with @Configuration and @Bean
  5. Component scanning with @Component, @Service, @Repository
  6. Bean lifecycle
  7. Spring MVC request handling
  8. Transactions with @Transactional
  9. Spring Data repositories
  10. Spring Boot auto-configuration

Avoid starting with everything at once. Spring feels magical when you learn MVC, JPA, transactions, Boot, security, and annotations simultaneously.


13. Tiny Example

Your code:

@Service
public class GreetingService {

    public String greet(String name) {
        return "Hello, " + name;
    }
}
@RestController
public class GreetingController {

    private final GreetingService greetingService;

    public GreetingController(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    @GetMapping("/hello")
    public String hello() {
        return greetingService.greet("World");
    }
}

What Spring does:

1. Finds GreetingService
2. Creates a GreetingService object
3. Finds GreetingController
4. Sees that it needs GreetingService
5. Injects GreetingService into GreetingController
6. Maps GET /hello to hello()
7. Calls hello() when an HTTP request arrives
8. Sends "Hello, World" as the response

That is Spring in miniature.


Bottom Line

Spring Framework mainly does four things:

  1. Object management
    It creates and manages your application objects.

  2. Dependency injection
    It wires objects together automatically.

  3. Infrastructure integration
    It provides transactions, web handling, validation, database integration, events, configuration, etc.

  4. Behavior wrapping
    It uses proxies to add behavior such as transactions, security, caching, and async execution.

The shortest explanation is:

Spring is a container and infrastructure framework that lets you write business code while it handles object creation, wiring, lifecycle, and common enterprise concerns.