Foundation · Understand

Why Spring Exists: From new to a Container

Begin with a tightly coupled Java object graph and discover the exact construction, configuration, lifecycle, and testability problems the Spring container solves.

Primary source: Spring Framework 6.2 Reference: The IoC Container

22 min

Lesson 1 of 14

1d streak

The Architectural Question

Annotations are impossible to reason about when Spring is introduced as syntax instead of a solution to object construction and application assembly.

What this guide proves

  • Object construction
  • Inversion of Control
  • Dependency Injection

The problem starts with object construction

A Java application is an object graph. Controllers depend on application services, services depend on repositories and clients, and infrastructure objects depend on configuration. The difficult part is not calling a method; it is constructing that graph consistently without making business classes responsible for infrastructure choices.

CheckoutService.java
final class CheckoutService {
    private final PaymentGateway gateway =
        new StripePaymentGateway(System.getenv("STRIPE_KEY"));

    Receipt checkout(Order order) {
        return gateway.charge(order.total());
    }
}

Construction leaked into business code

CheckoutService now chooses a vendor, reads environment configuration, controls the dependency lifecycle, and makes an isolated test difficult. Those are assembly decisions, not checkout rules.

Active Recall Checkpoint

Why is calling new not itself the problem?

Hint: Think about where construction happens and who chooses the implementation.

Show answer & explanation

new is ordinary Java and Spring uses constructors too. The design problem appears when a business object constructs and configures its own collaborators, coupling policy to assembly.

First fix the design with a composition root

Dependency injection does not require a framework. Accept collaborators through the constructor and assemble the graph at one application boundary. The service becomes explicit, testable, and independent of the selected gateway.

Application.java
interface PaymentGateway {
    Receipt charge(Money amount);
}

final class CheckoutService {
    private final PaymentGateway gateway;

    CheckoutService(PaymentGateway gateway) {
        this.gateway = gateway;
    }
}

var gateway = new StripePaymentGateway(stripeKey);
var checkoutService = new CheckoutService(gateway);

Architectural Boundary · Do Not Confuse

Concept A

Dependency Injection

Concept B

Inversion of Control

Dependency Injection is the concrete technique of supplying collaborators from outside an object. Inversion of Control is the broader design shift: framework or composition code controls assembly and calls application code at defined extension points.

The container automates the composition root

As an application grows, the manual composition root must handle hundreds of objects, configuration values, scopes, lifecycle callbacks, decorators, and conditional integrations. Spring moves that repeated assembly work into an IoC container. You provide configuration metadata; the container turns it into object recipes, resolves dependencies, and manages the resulting objects.

Bean

An object created, assembled, and managed by a Spring IoC container.

BeanDefinition

The container recipe describing a bean type, dependencies, scope, lifecycle, and other creation metadata.

BeanFactory

The foundational container contract for registering definitions and obtaining beans.

ApplicationContext

The application-facing container that adds events, resources, messages, environment integration, and automatic extension discovery.

  1. 01Application configuration describes the object recipes.
  2. 02The container registers those recipes as BeanDefinition metadata.
  3. 03The container creates objects and resolves constructor dependencies.
  4. 04Lifecycle extensions can validate, initialize, decorate, or proxy the objects.
  5. 05Application code receives ordinary Java references and calls ordinary methods.

Spring owns assembly—not your domain model

The container should assemble long-lived application collaborators such as services, repositories, clients, and configuration objects. It does not need to own every value created with new. Orders, money values, request DTOs, and temporary algorithm objects normally remain ordinary application objects.

Use it when

The object is an application collaborator with dependencies, configuration, lifecycle, cross-cutting behavior, or a shared scope.

Reconsider when

The object is short-lived domain data, a value object, a request payload, or a local implementation detail with no container responsibility.

Active Recall Checkpoint

If Spring creates a CheckoutService, is the service no longer ordinary Java?

Show answer & explanation

It remains an ordinary Java object. Spring controls how the instance is created, wired, initialized, and possibly wrapped; the class and its method semantics are still Java.

Build the interview answer from the runtime model

A strong 60-second answer

Spring solves application assembly. Instead of business classes constructing concrete collaborators, configuration metadata describes beans and their dependencies. The IoC container registers that metadata, creates the object graph, manages lifecycle and scopes, and can apply cross-cutting behavior through post-processors and proxies. Dependency Injection is the mechanism used to supply those collaborators.

Active Recall Checkpoint

Explain how IoC improves testing without saying “because Spring provides mocks”.

Show answer & explanation

IoC encourages dependencies to be supplied from outside the class. A unit test can therefore construct the class with a small fake or mock collaborator without starting Spring or real infrastructure.

Working Implementation

Code Walkthrough

Wire the same checkout service manually and through an ApplicationContext, then identify who owns construction in each version.

ManualVsContainerLab.java
package com.example.core;

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

public class ManualVsContainerLab {
    public interface PaymentGateway {
        String charge(double amount);
    }

    public static final class StripeGateway implements PaymentGateway {
        private final String apiKey;
        public StripeGateway(String apiKey) { this.apiKey = apiKey; }
        public String charge(double amount) {
            return "Charged $" + amount + " via Stripe (key: " + apiKey.substring(0, 4) + "***)";
        }
    }

    public static final class CheckoutService {
        private final PaymentGateway gateway;
        // Constructor injection: collaborators passed in from outside
        public CheckoutService(PaymentGateway gateway) { this.gateway = gateway; }
        public String checkout(double amount) { return gateway.charge(amount); }
    }

    @Configuration
    static class AppConfig {
        @Bean
        PaymentGateway paymentGateway() { return new StripeGateway("sk_live_994821"); }
        @Bean
        CheckoutService checkoutService(PaymentGateway gateway) { return new CheckoutService(gateway); }
    }

    public static void main(String[] args) {
        // 1. Manual composition root: developer creates and wires objects with new
        PaymentGateway manualGateway = new StripeGateway("sk_test_12345");
        CheckoutService manualService = new CheckoutService(manualGateway);
        System.out.println("Manual: " + manualService.checkout(49.99));

        // 2. Container composition root: Spring manages construction, resolution & shutdown
        try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
            CheckoutService containerService = context.getBean(CheckoutService.class);
            System.out.println("Container: " + containerService.checkout(99.00));
        }
    }
}
Object construction belongs at the composition root, not inside business classes like CheckoutService.
Constructor injection decouples business rules from concrete infrastructure, making unit testing possible with plain fakes.
The Spring container automates this composition root without changing the underlying Java class semantics.

Synthesis & Production Verification

Deliberate Failure Condition

  1. Break: Let a business service construct its database and email collaborators directly
  2. Observe: Tests need production-shaped infrastructure and replacing an implementation requires editing business code
  3. Repair: Move object construction to an explicit composition boundary; Spring can own that boundary without changing ordinary Java semantics.

Production Acceptance Criteria

  • Distinguish Inversion of Control from Dependency Injection
  • Explain why constructor injection makes dependencies visible
  • Describe what the container owns and what application code still owns

Architectural & Technical Review

Core Interview Questions

Q01What problem does the Spring IoC container solve?Show answer

It centralizes the construction and assembly of application collaborators. Instead of business objects selecting and constructing concrete infrastructure, configuration describes beans and dependencies; the container creates the graph, manages lifecycle and scopes, and applies extension points such as post-processors.

Q02How are Inversion of Control and Dependency Injection different?Show answer

Inversion of Control is the broader transfer of flow or assembly responsibility to framework or composition code. Dependency Injection is the specific technique of supplying an object’s collaborators from outside, usually through its constructor.

Q03Should every Java object be a Spring bean?Show answer

No. Long-lived application collaborators with configuration, lifecycle, shared scope, or cross-cutting behavior are good candidates. Domain values, request payloads, entities, and short-lived local objects normally remain ordinary objects.

Production Pitfalls

Critical Anti-Patterns

  • Pitfall: Calling dependency injection an annotation feature — Constructor injection is a Java design technique. Spring automates assembly but does not invent the dependency relationship.
  • Pitfall: Turning every value into a bean — Container ownership adds indirection. Keep short-lived domain data and local implementation details outside the context.

Primary Documentation & Specifications

Supplementary Module Resources