Spring Framework & the IoC Container

What Is Spring?

18 min Lesson 1 of 13

What Is Spring?

Spring is the most widely adopted application framework in the Java ecosystem. At its core it is a set of libraries and conventions that handle the plumbing every enterprise application needs — dependency wiring, transaction management, data access, security, and web infrastructure — so you can focus on writing business logic rather than boilerplate.

Understanding Spring starts with understanding the problem it was created to solve.

The Problem Spring Was Created to Solve

Before Spring, the dominant standard for building Java enterprise applications was J2EE (Java 2 Platform, Enterprise Edition). J2EE promised portability and a standardised programming model, but in practice it delivered something else: tight coupling between business logic and the container, heavyweight XML descriptors, entity beans that had to extend framework base classes, and unit tests that were nearly impossible to write because every object depended on a running application server.

Consider a typical pre-Spring scenario: you need a service that places orders. It must look up an EJB remote reference, cast it to an interface, call the method, and catch remote exceptions — all just to call a method on another object in the same JVM:

// Pre-Spring J2EE service — the problems are obvious public class OrderServiceBean implements SessionBean { public void placeOrder(Order order) throws RemoteException { try { Context ctx = new InitialContext(); Object ref = ctx.lookup("java:comp/env/ejb/InventoryService"); InventoryServiceHome home = (InventoryServiceHome) PortableRemoteObject.narrow(ref, InventoryServiceHome.class); InventoryService inventory = home.create(); inventory.reserve(order.getItems()); // finally — the actual business call } catch (NamingException | CreateException e) { throw new EJBException(e); } } }

The real problem here is not verbosity — it is coupling. The business logic is buried under infrastructure. You cannot test placeOrder without a JNDI server, an application server, and a deployed EJB. You cannot swap the inventory implementation for a stub in a test. The code is untestable in isolation.

The core insight of Spring: objects should declare what they need, not go out and fetch it. A service should receive an InventoryService as a constructor argument — the framework provides the instance. This is Inversion of Control, and it is the idea that Spring is built around.

What Spring Actually Is

Spring is not a single library — it is an ecosystem of over 20 focused projects, all built on a shared foundation called the Spring Framework (sometimes called "Spring Core"). The Spring Framework provides:

  • The IoC Container — creates and wires objects (beans) together, injecting dependencies automatically.
  • Aspect-Oriented Programming (AOP) — applies cross-cutting concerns (logging, security, transactions) without polluting business classes.
  • Data Access AbstractionJdbcTemplate, transaction management, and integration with JPA/Hibernate.
  • Spring MVC — a clean, annotation-driven web framework on top of the Servlet API.
  • Testing Support — utilities for integration tests that spin up the Spring container without a full server.

Around this core, the broader Spring ecosystem adds:

  • Spring Boot — auto-configuration and embedded servers; lets you run a production-ready app from main() in minutes.
  • Spring Security — authentication, authorisation, CSRF protection, OAuth2/OIDC.
  • Spring Data — repository abstractions for JPA, MongoDB, Redis, and more.
  • Spring Cloud — service discovery, distributed configuration, circuit breakers for microservices.
  • Spring Batch — chunk-oriented processing for large-scale batch jobs.
Start with the framework, not the ecosystem. Spring Boot is convenient, but if you learn Spring Boot first you will not understand why things work. This tutorial focuses on Spring Framework (the IoC container) so that when Boot generates configuration for you, you can read, override, and debug it.

The Same Scenario With Spring

Here is the same OrderService rewritten as a Spring-managed bean. The business logic is now all that remains:

import org.springframework.stereotype.Service; @Service public class OrderService { private final InventoryService inventoryService; // Spring injects the dependency through the constructor public OrderService(InventoryService inventoryService) { this.inventoryService = inventoryService; } public void placeOrder(Order order) { inventoryService.reserve(order.getItems()); } }

No JNDI lookup. No container dependency. No RemoteException. The class is a plain Java object. You can instantiate it in a unit test by passing a mock InventoryService — no application server required:

// A pure JUnit test — no Spring container at all @Test void placeOrder_reservesItems() { InventoryService mockInventory = mock(InventoryService.class); OrderService service = new OrderService(mockInventory); Order order = new Order(List.of(new Item("SKU-42", 2))); service.placeOrder(order); verify(mockInventory).reserve(order.getItems()); }
Plain Old Java Objects (POJOs): Spring beans do not extend any framework class and do not implement any framework interface. They are ordinary Java classes. This is a deliberate design principle — your domain model stays clean and portable.

How Spring Fits Into Modern Java Development

Jakarta EE (the successor to J2EE) has caught up considerably — CDI provides a standardised dependency-injection model, and modern EE containers are far lighter. Spring and Jakarta EE are no longer as far apart as they once were.

In practice, Spring still dominates for several reasons:

  • Ecosystem breadth — Spring Data, Spring Security, and Spring Cloud have no direct equivalents of the same maturity.
  • Spring Boot's developer experience — a new service in under five minutes, with production-ready defaults.
  • Community and documentation quality — the reference docs, guides, and Stack Overflow coverage are exceptional.
  • Gradual adoption — you can add Spring to an existing plain-Java project without rewriting anything.
Spring 6 / Spring Boot 3 require Java 17+ and use jakarta.* imports (not javax.*). If you are reading older tutorials that import javax.servlet or javax.persistence, those apply to Spring 5 and earlier. All code in this tutorial uses the modern jakarta.* namespace.

Summary

Spring was born out of frustration with J2EE's tight coupling and untestability. Its core answer was simple: let the framework manage object creation and wiring, and keep your classes as plain Java objects. Everything else in the Spring ecosystem — Boot, Security, Data, Cloud — is built on top of that foundation. In the next lesson you will examine the principle that makes it all possible: Inversion of Control.