What is Spring? Spring Framework vs Spring Boot Explained

Introduction

This tutorial explains what Spring is, and how the Spring Framework and Spring Boot relate to each other - two names that beginners often take to be the same thing, or two competing things. We will start with the problem Spring was created to solve, and build one small order-processing application three times: in plain Java, with the Spring Framework, and with Spring Boot. Running each version shows exactly what each layer adds. We will then look at the modules of the Spring Framework, compare the Framework with Boot side by side, and finish with a tour of the wider Spring ecosystem and the versions this Spring series uses.

(This is the first tutorial of the Spring series on JavaBrahman. It assumes you know core Java - classes, interfaces and objects. If you use Maven or Gradle for the first time, the introduction to Maven and GradleClick to read: introduction to Maven and Gradle covers what you need.)

What is the Spring Framework?

The Spring Framework is an open-source framework for building Java applications. It started in 2003, from Rod Johnson's book Expert One-on-One J2EE Design and Development, as a lighter alternative to the Enterprise JavaBeans (EJB) model of the time, which forced application classes to extend framework types and run inside a heavyweight application server.

Spring's central idea is that your business classes stay plain Java objects (POJOs), and a container creates them and connects them to each other. This is called Inversion of Control (IoC): instead of your code creating the objects it needs, the container creates them and hands them in - which is Dependency Injection. Everything else Spring offers - transactions, web applications, security, data access - is built on top of this container.

(The difference between Inversion of Control, Dependency Injection and the Dependency Inversion Principle is explained in the tutorial Dependency Injection vs Dependency Inversion vs IoCClick to read: Dependency Injection vs Dependency Inversion vs IoC.)

The problem Spring solves: wiring objects by hand

Our example application places an order and sends a confirmation. It has three classes: an OrderService, a PaymentGateway that charges the customer, and a Notifier that sends the confirmation. In plain Java, the service creates its own collaborators with new -
Plain Java - OrderService creates its own dependencies
//PlainJavaApp.java
package com.javabrahman.spring.intro.plain;

class PaymentGateway {
  boolean charge(String customer, double amount) {
    System.out.println("Charging " + customer + " $" + amount);
    return true;
  }
}

class Notifier {
  void send(String customer, String message) {
    System.out.println("Email to " + customer + ": " + message);
  }
}

class OrderService {
  // The service decides which implementations it uses, and creates them itself
  private final PaymentGateway paymentGateway = new PaymentGateway();
  private final Notifier notifier = new Notifier();

  void placeOrder(String customer, double amount) {
    if (paymentGateway.charge(customer, amount)) {
      notifier.send(customer, "Your order is confirmed");
    }
  }
}

public class PlainJavaApp {
  public static void main(String[] args) {
    OrderService orderService = new OrderService();
    orderService.placeOrder("Tom Jones", 49.99);
  }
}
 OUTPUT of the above code
Charging Tom Jones $49.99
Email to Tom Jones: Your order is confirmed
What is wrong with this design?
  • Tight coupling: OrderService is welded to one concrete PaymentGateway and one Notifier. Switching to a different payment provider, or to SMS notifications, means editing OrderService.
  • Hard to test: a unit test of OrderService cannot replace the real gateway with a fake one, so every test would really charge a card.
  • Wiring spreads everywhere: in a real application with hundreds of classes, every class creates its own dependencies, often several times over, and nobody can see in one place how the application is assembled.
The usual fix is to pass the dependencies in through the constructor. That fixes coupling and testing, but someone still has to create every object in the right order and pass it to the right constructor. In a large application that job is big enough to want a tool for it - and that tool is the Spring container.

The same application with the Spring Framework

With Spring, the classes ask for their dependencies in their constructors, and are marked with @Component so that Spring knows to manage them. A small configuration class tells Spring which package to scan, and the main() method starts a Spring application context - the container - and asks it for the ready-made OrderService -
Spring Framework - the container creates and connects the objects
//SpringFrameworkApp.java
package com.javabrahman.spring.intro.framework;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Component
class PaymentGateway {
  boolean charge(String customer, double amount) {
    System.out.println("Charging " + customer + " $" + amount);
    return true;
  }
}

@Component
class Notifier {
  void send(String customer, String message) {
    System.out.println("Email to " + customer + ": " + message);
  }
}

@Component
class OrderService {
  private final PaymentGateway paymentGateway;
  private final Notifier notifier;

  // Spring sees this constructor, finds a PaymentGateway and a Notifier bean, and passes them in
  OrderService(PaymentGateway paymentGateway, Notifier notifier) {
    this.paymentGateway = paymentGateway;
    this.notifier = notifier;
  }

  void placeOrder(String customer, double amount) {
    if (paymentGateway.charge(customer, amount)) {
      notifier.send(customer, "Your order is confirmed");
    }
  }
}

@Configuration
@ComponentScan   // scan this class's package for @Component classes
class AppConfig {
}

public class SpringFrameworkApp {
  public static void main(String[] args) {
    try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
      OrderService orderService = context.getBean(OrderService.class);
      orderService.placeOrder("Tom Jones", 49.99);
      System.out.println("Beans defined in the context: " + context.getBeanDefinitionCount());
    }
  }
}
 OUTPUT of the above code
Charging Tom Jones $49.99
Email to Tom Jones: Your order is confirmed
Beans defined in the context: 9

Explanation of the code

  • @Component marks a class as a bean - an object whose creation and wiring Spring manages.
  • OrderService no longer calls new. It declares what it needs in its constructor, and Spring injects the matching beans when it creates it.
  • @Configuration with @ComponentScan tells Spring to look for @Component classes in the configuration class's package (com.javabrahman.spring.intro.framework).
  • AnnotationConfigApplicationContext is the container. It reads AppConfig, creates the three beans in the right order (the gateway and notifier before the service that needs them), and keeps them ready. The try-with-resources block closes the context at the end.
  • The output is the same as before - the business logic did not change. What changed is who creates the objects. The context holds 9 bean definitions: our 3 components, AppConfig itself, and 5 internal beans Spring registers to process annotations such as @Configuration, constructor injection and event listeners.

What the Spring Framework contains

The container is only the core. The Spring Framework is a set of modules you can use independently, all built around it -
  • Core container (spring-core, spring-beans, spring-context, spring-expression): beans, dependency injection, the application context, events, resources, and the Spring Expression Language.
  • AOP (spring-aop): aspect-oriented programming, which lets Spring add behaviour such as transactions or caching around your methods without changing them.
  • Data access (spring-jdbc, spring-tx, spring-orm): JDBC helpers, declarative transactions with @Transactional, and integration with JPA and Hibernate.
  • Web (spring-webmvc, spring-webflux): Spring MVC for servlet-based web applications and REST APIs, and Spring WebFlux for reactive ones.
  • Testing (spring-test): loading an application context inside a test, and mock web requests.
  • Integration: messaging, scheduling, caching, remoting and more.
The Spring Framework gives you the building blocks, but you still decide which modules to add, which versions work together, and how to configure each one - for a web application that means setting up a web server, a servlet, JSON conversion, logging and so on yourself.

What is Spring Boot?

Spring Boot, first released in 2014, is a project built on top of the Spring Framework that removes most of that setup work. It does not replace the Framework - a Spring Boot application is a Spring Framework application, with the same container, beans and annotations. What Boot adds is -
  • Auto-configuration: Boot looks at the libraries on your classpath and configures sensible beans for them. Add a database driver and you get a connection pool; add Spring MVC and you get a configured web server.
  • Starters: one dependency, such as spring-boot-starter-web, brings a tested set of libraries whose versions are known to work together.
  • An embedded server: web applications run as a plain java -jar app.jar, with Tomcat or Jetty inside the jar, instead of being deployed to a separate server.
  • Externalized configuration: settings in application.properties or application.yml, overridable by environment variables and command-line arguments.
  • Production features: health checks, metrics and other operational endpoints through Spring Boot Actuator.

The same application with Spring Boot

Here is the order application once more, as a Spring Boot application. The three component classes are exactly the same as in the Spring Framework version; only the startup code changes -
Spring Boot - the same beans, started by SpringApplication
//SpringBootApp.java
package com.javabrahman.spring.intro.boot;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;

@Component
class PaymentGateway {
  boolean charge(String customer, double amount) {
    System.out.println("Charging " + customer + " $" + amount);
    return true;
  }
}

@Component
class Notifier {
  void send(String customer, String message) {
    System.out.println("Email to " + customer + ": " + message);
  }
}

@Component
class OrderService {
  private final PaymentGateway paymentGateway;
  private final Notifier notifier;

  OrderService(PaymentGateway paymentGateway, Notifier notifier) {
    this.paymentGateway = paymentGateway;
    this.notifier = notifier;
  }

  void placeOrder(String customer, double amount) {
    if (paymentGateway.charge(customer, amount)) {
      notifier.send(customer, "Your order is confirmed");
    }
  }
}

// Runs once the application has started
@Component
class PlaceOrderOnStartup implements CommandLineRunner {
  private final OrderService orderService;

  PlaceOrderOnStartup(OrderService orderService) {
    this.orderService = orderService;
  }

  @Override
  public void run(String... args) {
    orderService.placeOrder("Tom Jones", 49.99);
  }
}

@SpringBootApplication
public class SpringBootApp {
  public static void main(String[] args) {
    try (ConfigurableApplicationContext context = SpringApplication.run(SpringBootApp.class, args)) {
      System.out.println("Beans defined in the context: " + context.getBeanDefinitionCount());
    }
  }
}
 OUTPUT of the above code
Charging Tom Jones $49.99
Email to Tom Jones: Your order is confirmed
Beans defined in the context: 54

Explanation of the code

  • @SpringBootApplication combines three annotations: @Configuration, @ComponentScan (the same scanning as before), and @EnableAutoConfiguration, which switches on Boot's auto-configuration.
  • SpringApplication.run() creates the application context, loads configuration from application.properties and the environment, sets up logging, applies auto-configuration, and then calls every CommandLineRunner bean - here PlaceOrderOnStartup, which places the order.
  • The context now holds 54 bean definitions instead of 9. The extra beans come from auto-configuration: with only the basic spring-boot-starter on the classpath, Boot still configured beans for property placeholders, configuration-property binding, a task executor and a task scheduler, SSL bundles, application availability and AOP proxying - all with no code from us. Add a web starter and the count grows again, with a web server, JSON mapping and request handling.
  • Spring Boot normally also prints a banner and a few startup log lines, ending with one such as Started SpringBootApp in ... seconds. They are switched off in these examples so that the output shows only what our code prints.

Spring Framework vs Spring Boot

Putting the two side by side -
  • What it is: the Spring Framework is the foundation - the container and the modules. Spring Boot is a layer on top that configures the Framework for you.
  • Configuration: with the Framework you declare the beans for every library you use. With Boot, auto-configuration declares them, and you only override what you want to change.
  • Dependencies: with the Framework you choose compatible versions of each library. With Boot, the starters and the Boot parent POM choose them.
  • Running: a classic Framework web application is packaged as a WAR and deployed to a server. A Boot application is usually a self-contained jar run with java -jar.
  • When to use which: for almost every new application, use Spring Boot. Learn the Framework's concepts anyway - beans, injection, scopes, lifecycle - because Boot applications are built from them, and every Boot behaviour is a Framework feature configured for you.
That is why this series starts with the Spring Framework core (this module) before moving to Spring Boot: once you know what a bean and the container are, auto-configuration stops being magic.

The Spring ecosystem

Beyond the Framework and Boot, "Spring" is a family of projects, each solving one area and each built on the same container -
  • Spring Data: repositories for relational and NoSQL databases (JPA, JDBC, MongoDB, Redis and more), where you declare an interface and Spring writes the queries.
  • Spring Security: authentication and authorization - logins, OAuth2, JWT, method-level security.
  • Spring Cloud: tools for distributed systems - configuration servers, service discovery, API gateways and circuit breakers.
  • Spring Batch and Spring Integration: batch jobs and enterprise integration patterns.
  • Spring for Apache Kafka and Spring AMQP: messaging with Kafka and RabbitMQ.
  • Spring Modulith: structuring a single application into well-defined modules.
  • Spring AI: a portable API for large language models, embeddings, vector stores and tool calling.
Each of these projects has a Spring Boot starter, so adding one to a Boot application is usually one dependency plus a few properties.

Versions used in this series

The examples in this series use Spring Boot 4.1.2, which brings Spring Framework 7.0.10 - the latest stable releases at the time of writing. Spring Framework 7 and Spring Boot 4 require Java 17 or later, use the jakarta.* package names of Jakarta EE 11 (not the old javax.* names), and add null-safety annotations from JSpecify. Every example was compiled and run on Java 25, and each output box shows the real output of that run.

Summary

In this tutorial we first saw what the Spring Framework is: a container that creates your plain Java objects and connects them through dependency injection. We wrote an order application in plain Java and saw the tight coupling of objects that create their own dependencies. We then rebuilt it with the Spring Framework, where an application context creates and wires the beans, and looked at the Framework's modules. Next we saw what Spring Boot adds on top - auto-configuration, starters, an embedded server and externalized configuration - and ran the same application as a Spring Boot application. We compared the Framework with Boot, toured the wider Spring ecosystem, and noted the versions this series uses. The next tutorial looks closely at the container itself: the Spring IoC container and ApplicationContextClick to read tutorial: Spring IoC Container and ApplicationContext Explained.