Spring Boot: From Basics to Production · Spring Core and Spring Boot Fundamentals

Dependency Injection and Beans

Let Spring create and wire your components through constructors.

Create a free account to save this page, take notes and track your progress. Sign in

Spring's IoC container creates objects called beans and injects their dependencies. Mark classes with stereotypes (@Component, @Service, @Repository, @RestController) or declare beans with @Bean methods in @Configuration classes (useful for third-party types). Prefer constructor injection with final fields — dependencies are explicit, immutable and easy to test. With a single constructor, no @Autowired is needed.

public interface PaymentGateway {
    PaymentResult charge(Money amount, String token);
}

@Service
public class CheckoutService {
    private final OrderRepository orders;
    private final PaymentGateway payments;

    public CheckoutService(OrderRepository orders, PaymentGateway payments) {   // constructor injection
        this.orders = orders;
        this.payments = payments;
    }

    public Order checkout(Cart cart, String token) {
        var result = payments.charge(cart.total(), token);
        return orders.save(Order.paid(cart, result.reference()));
    }
}

@Configuration
class PaymentConfig {
    @Bean
    PaymentGateway paymentGateway(StripeProperties props) {
        return new StripeGateway(props.apiKey());
    }
}

Common pitfalls

  • Field injection (@Autowired private Foo foo;) — hides dependencies and complicates tests.
  • Circular dependencies between services (a design smell; Boot fails fast by default).
  • Multiple beans of the same type without @Primary or @Qualifier.

Key points

  • Beans are container-managed objects
  • Constructor injection with final fields
  • @Bean for third-party classes

Further reading: Spring: Dependency injection

Practice questions

Why is constructor injection preferred over field injection?

Answer

Dependencies are explicit and final, objects can't be created half-initialized, tests can instantiate classes with plain new, and circular dependencies are detected.

How do you resolve two beans implementing the same interface?

Answer

Mark one @Primary, or inject a specific one with @Qualifier("name"), or inject List<Interface>/Map<String, Interface> to get all.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?