Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

👥10,105visitors

Built for engineers who want to go deep.


← Spring Boot REST API Development

Spring Boot Basics

  • What is Spring Boot?
  • Dependency Injection
  • Building REST Controllers

Validation & Error Handling

  • Bean Validation with @Valid
  • Global Exception Handling

Spring Framework Internals

  • IoC Container Fundamentals
  • Bean Lifecycle In Detail
  • Component Scanning & Configuration
  • Auto-Configuration Mechanism
  • Spring AOP
  • @Transactional Deep Dive

Microservices Architecture

  • Monolith to Microservices Decomposition
  • API Gateway
  • Service Discovery
  • Inter-Service Communication Choices
  • Event-Driven Architecture Patterns
  • Messaging Technology Choices

Resilience Patterns

  • Configuration Management
  • Circuit Breaker Pattern
  • Retry & Backoff Strategies
  • Bulkhead & Rate Limiting
  • Timeout Strategy
  • Why Microservices Fail

Distributed Data & Consistency Patterns

  • Two-Phase Commit
  • Saga Pattern
  • Outbox Pattern
  • Eventual Consistency Design
  • CQRS Basics
  • CAP Theorem

Observability & Operations

  • Centralized Logging
  • Distributed Tracing
  • Metrics & Monitoring
  • Alerting Strategy
  • Health Checks

Payment Systems

  • Payment — Requirements
  • Payment — Core Flow
  • Payment — Idempotency Implementation
  • Payment — Failure Handling & Reconciliation
  • Payment — Security

Order Management System

  • OMS — Requirements
  • OMS — State Machine Design
Chaturmind
← Spring Boot REST API Development

Spring Boot Basics

  • What is Spring Boot?
  • Dependency Injection
  • Building REST Controllers

Validation & Error Handling

  • Bean Validation with @Valid
  • Global Exception Handling

Spring Framework Internals

  • IoC Container Fundamentals
  • Bean Lifecycle In Detail
  • Component Scanning & Configuration
  • Auto-Configuration Mechanism
  • Spring AOP
  • @Transactional Deep Dive

Microservices Architecture

  • Monolith to Microservices Decomposition
  • API Gateway
  • Service Discovery
  • Inter-Service Communication Choices
  • Event-Driven Architecture Patterns
  • Messaging Technology Choices

Resilience Patterns

  • Configuration Management
  • Circuit Breaker Pattern
  • Retry & Backoff Strategies
  • Bulkhead & Rate Limiting
  • Timeout Strategy
  • Why Microservices Fail

Distributed Data & Consistency Patterns

  • Two-Phase Commit
  • Saga Pattern
  • Outbox Pattern
  • Eventual Consistency Design
  • CQRS Basics
  • CAP Theorem

Observability & Operations

  • Centralized Logging
  • Distributed Tracing
  • Metrics & Monitoring
  • Alerting Strategy
  • Health Checks

Payment Systems

  • Payment — Requirements
  • Payment — Core Flow
  • Payment — Idempotency Implementation
  • Payment — Failure Handling & Reconciliation
  • Payment — Security

Order Management System

  • OMS — Requirements
  • OMS — State Machine Design
HomeLearnSpring BootSpring Boot REST API DevelopmentSpring Boot Basics
✓ FreeBeginner· 6 min read

Dependency Injection

IoC container, @Component, @Service, @Autowired — the core of Spring.

Published September 21, 2026


Dependency Injection

Almost every class depends on others: an OrderService needs an OrderRepository to save orders and a PaymentClient to charge cards. The question is who creates those dependencies.

// Without DI: the class builds its own dependencies
public class OrderService {
    private final OrderRepository repo = new MongoOrderRepository("mongodb://prod-host/shop");
    private final PaymentClient payments = new StripePaymentClient(System.getenv("STRIPE_KEY"));
}

This hard-wires everything. You can't test OrderService without a real database and a real payment provider. Swapping Stripe for another provider means editing the class. Configuration details leak everywhere.

Dependency injection (DI) flips it around: a class declares what it needs, usually as constructor parameters, and something else supplies those objects:

public class OrderService {
    private final OrderRepository repo;
    private final PaymentClient payments;

    public OrderService(OrderRepository repo, PaymentClient payments) {   // "give me these"
        this.repo = repo;
        this.payments = payments;
    }
}

The class now depends on interfaces, not on specific implementations or their configuration. In a test you pass fakes: new OrderService(new InMemoryOrderRepository(), new FakePaymentClient()). In production, Spring creates the real ones and passes them in. This handing-over of object creation to a container is called Inversion of Control (IoC).

Advertisement

The Spring container

Spring's ApplicationContext is the IoC container. At startup it:

  1. Finds bean definitions: classes annotated with @Component (and its specializations), plus @Bean methods in @Configuration classes.
  2. Works out the dependency graph from constructor parameters and other injection points.
  3. Creates the beans in the right order, injects their dependencies, and runs lifecycle callbacks.
  4. Hands out the finished, wired objects, and fails fast at startup if something is missing or ambiguous.

The objects it manages are called beans.

Declaring beans

@Service                    // your own classes: annotate, and component scanning finds them
public class OrderService { ... }

@Configuration
public class ClientsConfig {
    @Bean                   // classes you don't own, or that need custom construction
    public PaymentClient paymentClient(PaymentProperties props) {
        return new StripePaymentClient(props.apiKey(), props.timeout());
    }
}
AnnotationMeaning
@ComponentA generic Spring-managed class
@ServiceBusiness logic. Same behaviour as @Component; documents intent
@RepositoryData access. Also enables translation of persistence exceptions into Spring's DataAccessException
@Controller / @RestControllerWeb layer: request handlers
@Configuration + @BeanFactory methods for beans that need explicit construction

Three ways to inject, and which to use

// 1. Constructor injection — RECOMMENDED
@Service
public class OrderService {
    private final OrderRepository repo;
    public OrderService(OrderRepository repo) { this.repo = repo; }   // single constructor: no @Autowired needed
}

// 2. Setter injection — for genuinely optional dependencies
@Autowired(required = false)
public void setAuditLog(AuditLog auditLog) { this.auditLog = auditLog; }

// 3. Field injection — avoid
@Autowired private OrderRepository repo;

Why constructor injection is preferred:

  • Required dependencies can't be forgotten. The object can't exist without them, and fields can be final.
  • Dependencies are visible in one place. A constructor with 8 parameters is an honest signal that the class does too much.
  • Tests need no Spring: just call the constructor with fakes.
  • Circular dependencies fail loudly at startup instead of being quietly half-wired.

Field injection hides dependencies, prevents final, and makes plain unit tests impossible without reflection or a Spring context.

When there's more than one candidate

If two beans implement PaymentClient, Spring can't guess which one to inject, and startup fails with NoUniqueBeanDefinitionException. Resolve it explicitly:

@Primary @Component class StripePaymentClient implements PaymentClient { }     // the default choice

@Component @Qualifier("paypal") class PaypalPaymentClient implements PaymentClient { }

public CheckoutService(@Qualifier("paypal") PaymentClient client) { ... }      // pick a specific one

public PaymentRouter(List<PaymentClient> all) { ... }                          // or inject ALL of them
public PaymentRouter(Map<String, PaymentClient> byBeanName) { ... }            // … keyed by bean name

Injecting a List or Map of implementations is a clean way to build strategy or plugin patterns without if/else chains.

Scopes: how many instances?

ScopeInstancesTypical use
singleton (default)One per containerStateless services, repositories, clients
prototypeNew one each time it's requestedStateful, short-lived helpers
request / sessionOne per HTTP request / sessionRequest-scoped context (web apps)

Because the default is singleton, one instance serves all concurrent requests. So singleton beans must be stateless or thread-safe. A mutable field in a @Service that holds per-request data is a classic concurrency bug.

A subtle trap: injecting a prototype bean into a singleton gives you one prototype instance, created once, not a fresh one per use. Use ObjectProvider<T> to get a new instance when needed.

Conditional and profile-specific beans

@Bean
@Profile("dev")                                                        // only in the dev profile
PaymentClient fakePayments() { return new FakePaymentClient(); }

@Bean
@ConditionalOnProperty(name = "features.sms", havingValue = "true")   // only when a flag is on
SmsNotifier smsNotifier() { ... }

This is also the mechanism behind Spring Boot's auto-configuration (see What is Spring Boot?).

Circular dependencies

If A needs B and B needs A through constructors, Spring can't create either one first, and startup fails. Spring Boot disallows circular references by default since version 2.6. The right fix is almost always design: extract the shared logic into a third bean, or replace a direct call with an event. @Lazy injection works around it, but hides the smell.

Follow-up questions this topic invites — and their answers

Q: What's the difference between IoC and DI? A: Inversion of Control is the general principle that a framework, not your code, controls object creation and flow. Dependency Injection is the specific technique Spring uses to implement it: supplying an object's dependencies from outside instead of having it construct them.

Q: Why is constructor injection recommended over field injection? A: It makes dependencies explicit and mandatory, allows final fields and immutability, enables plain unit tests without Spring, and surfaces circular dependencies at startup. Field injection hides dependencies and requires reflection or a container to test.

Q: What's the difference between @Component, @Service and @Repository? A: All three register the class as a bean via component scanning. @Service and @Component behave identically, and the name documents the layer. @Repository additionally enables exception translation, converting database-specific exceptions into Spring's DataAccessException hierarchy.

Q: Are Spring beans thread-safe? A: Spring doesn't make them thread-safe. Singleton beans are shared by all threads, so they must not hold mutable per-request state in fields. Keep them stateless, or use thread-safe structures, or request-scoped beans for per-request data.

Q: How does Spring choose between two beans of the same type? A: It fails with an ambiguity error unless you guide it. Mark one @Primary, select one with @Qualifier, match by parameter name as a fallback, or inject all candidates as a List/Map and choose at runtime.

Advertisement

Previous

What is Spring Boot?

Next

Building REST Controllers

AI Tutor

Lesson: Dependency Injection

Quick actions

AI responses can be inaccurate. Verify critical information.